rulesync 16.31.0 → 16.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -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-Cwe8RKJs.cjs → import-0wSuIchf.cjs} +1417 -23
- package/dist/{import-D2GhBXJV.js → import-D5n_V39C.js} +1401 -7
- package/dist/import-D5n_V39C.js.map +1 -0
- 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",
|
|
@@ -31185,6 +31659,230 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
31185
31659
|
}
|
|
31186
31660
|
};
|
|
31187
31661
|
//#endregion
|
|
31662
|
+
//#region src/features/mcp/continue-mcp.ts
|
|
31663
|
+
/**
|
|
31664
|
+
* Parse a Continue MCP file. Continue reads the files under `mcpServers/` as
|
|
31665
|
+
* JSONC, so comments and trailing commas in a hand-written file are accepted;
|
|
31666
|
+
* malformed content or a non-object root (`null`, an array, a scalar) fails
|
|
31667
|
+
* closed rather than being spread into the regenerated file.
|
|
31668
|
+
*/
|
|
31669
|
+
function parseContinueMcpConfig({ fileContent, relativePath }) {
|
|
31670
|
+
let parsed;
|
|
31671
|
+
try {
|
|
31672
|
+
parsed = parseJsonc$6(fileContent);
|
|
31673
|
+
} catch (error) {
|
|
31674
|
+
throw new Error(`Failed to parse Continue MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
|
|
31675
|
+
}
|
|
31676
|
+
if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Continue MCP config at ${relativePath}: expected a JSON object at the root`);
|
|
31677
|
+
return parsed;
|
|
31678
|
+
}
|
|
31679
|
+
/**
|
|
31680
|
+
* The remote transport Continue reads a server as. Continue's JSON schema
|
|
31681
|
+
* accepts `type: "sse"` or `type: "http"` (streamable HTTP) for a `url`
|
|
31682
|
+
* server; a bare `url` defaults to `http`, and the canonical
|
|
31683
|
+
* `streamable-http` spelling is folded into it. A `ws(s)://` URL or any other
|
|
31684
|
+
* stated transport has no Continue equivalent, so `undefined` tells the
|
|
31685
|
+
* caller to skip the server.
|
|
31686
|
+
* @see https://docs.continue.dev/customize/deep-dives/mcp
|
|
31687
|
+
*/
|
|
31688
|
+
function asContinueRemoteType(stated, url) {
|
|
31689
|
+
if (stated === "sse") return "sse";
|
|
31690
|
+
if (stated === "http" || stated === "streamable-http") return "http";
|
|
31691
|
+
if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
|
|
31692
|
+
}
|
|
31693
|
+
/**
|
|
31694
|
+
* Convert the canonical server map to the shape Continue's JSON MCP schema
|
|
31695
|
+
* accepts: a remote server is `{ type, url, headers? }` and a stdio server is
|
|
31696
|
+
* `{ type: "stdio", command, args?, env? }`. Continue validates the whole
|
|
31697
|
+
* file with a strict union — one server that matches neither shape (an
|
|
31698
|
+
* unknown transport, a non-string `env` value) makes Continue drop every
|
|
31699
|
+
* server in the file — so only the documented keys are emitted and anything
|
|
31700
|
+
* else (`timeout`, `oauth`, rulesync-only fields) is left out. `envFile` is
|
|
31701
|
+
* accepted by Continue's schema but ignored with a warning at load time, so
|
|
31702
|
+
* it is dropped here with a warning instead of being written as if it worked.
|
|
31703
|
+
*
|
|
31704
|
+
* A server Continue cannot start or reach — no transport at all, a remote
|
|
31705
|
+
* transport without a URL, a WebSocket URL, or a stdio entry without a
|
|
31706
|
+
* command — is skipped with a warning rather than written in a form that
|
|
31707
|
+
* would invalidate the file.
|
|
31708
|
+
* @see https://docs.continue.dev/customize/deep-dives/mcp
|
|
31709
|
+
*/
|
|
31710
|
+
function convertToContinueFormat(mcpServers, logger) {
|
|
31711
|
+
const result = {};
|
|
31712
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
31713
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
31714
|
+
if (declaresNoTransport(serverConfig)) {
|
|
31715
|
+
warnAndSkipMcpServer({
|
|
31716
|
+
toolName: "Continue",
|
|
31717
|
+
serverName,
|
|
31718
|
+
reason: "no transport",
|
|
31719
|
+
logger
|
|
31720
|
+
});
|
|
31721
|
+
continue;
|
|
31722
|
+
}
|
|
31723
|
+
const converted = isRemoteMcpServer(serverConfig) ? convertRemoteServer({
|
|
31724
|
+
serverName,
|
|
31725
|
+
serverConfig,
|
|
31726
|
+
logger
|
|
31727
|
+
}) : convertStdioServer({
|
|
31728
|
+
serverName,
|
|
31729
|
+
serverConfig,
|
|
31730
|
+
logger
|
|
31731
|
+
});
|
|
31732
|
+
if (converted !== void 0) result[serverName] = converted;
|
|
31733
|
+
}
|
|
31734
|
+
return result;
|
|
31735
|
+
}
|
|
31736
|
+
function convertRemoteServer({ serverName, serverConfig, logger }) {
|
|
31737
|
+
const url = resolveRemoteMcpUrl(serverConfig);
|
|
31738
|
+
if (!url) {
|
|
31739
|
+
warnAndSkipMcpServer({
|
|
31740
|
+
toolName: "Continue",
|
|
31741
|
+
serverName,
|
|
31742
|
+
reason: "a remote transport without a url",
|
|
31743
|
+
logger
|
|
31744
|
+
});
|
|
31745
|
+
return;
|
|
31746
|
+
}
|
|
31747
|
+
const stated = serverConfig.type ?? serverConfig.transport;
|
|
31748
|
+
const remoteType = asContinueRemoteType(typeof stated === "string" ? stated : void 0, url);
|
|
31749
|
+
if (remoteType === void 0) {
|
|
31750
|
+
warnAndSkipMcpServer({
|
|
31751
|
+
toolName: "Continue",
|
|
31752
|
+
serverName,
|
|
31753
|
+
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)`,
|
|
31754
|
+
logger
|
|
31755
|
+
});
|
|
31756
|
+
return;
|
|
31757
|
+
}
|
|
31758
|
+
const converted = {
|
|
31759
|
+
type: remoteType,
|
|
31760
|
+
url
|
|
31761
|
+
};
|
|
31762
|
+
if (isRecord$1(serverConfig.headers)) converted.headers = omitPrototypePollutionKeys(serverConfig.headers);
|
|
31763
|
+
return converted;
|
|
31764
|
+
}
|
|
31765
|
+
function convertStdioServer({ serverName, serverConfig, logger }) {
|
|
31766
|
+
const [command, ...args] = (typeof serverConfig.command === "string" ? serverConfig.command !== "" : Array.isArray(serverConfig.command) && serverConfig.command.length > 0) ? resolveLocalMcpCommand(serverConfig) : [];
|
|
31767
|
+
if (!command) {
|
|
31768
|
+
warnAndSkipMcpServer({
|
|
31769
|
+
toolName: "Continue",
|
|
31770
|
+
serverName,
|
|
31771
|
+
reason: "a stdio transport without a command",
|
|
31772
|
+
logger
|
|
31773
|
+
});
|
|
31774
|
+
return;
|
|
31775
|
+
}
|
|
31776
|
+
const converted = {
|
|
31777
|
+
type: "stdio",
|
|
31778
|
+
command
|
|
31779
|
+
};
|
|
31780
|
+
if (args.length > 0) converted.args = args;
|
|
31781
|
+
if (isRecord$1(serverConfig.env)) converted.env = omitPrototypePollutionKeys(serverConfig.env);
|
|
31782
|
+
if (typeof serverConfig.envFile === "string") logger?.warn(`Continue ignores "envFile" for MCP servers, so the envFile of server "${serverName}" was not written; put the variables in "env" instead.`);
|
|
31783
|
+
return converted;
|
|
31784
|
+
}
|
|
31785
|
+
/**
|
|
31786
|
+
* Convert Continue's server map back to the canonical shape. The documented
|
|
31787
|
+
* spelling (`type` + `command`/`url`) is already canonical, so entries pass
|
|
31788
|
+
* through with only prototype-pollution keys dropped.
|
|
31789
|
+
*/
|
|
31790
|
+
function convertFromContinueFormat(mcpServers) {
|
|
31791
|
+
if (!isMcpServers(mcpServers)) return {};
|
|
31792
|
+
const result = {};
|
|
31793
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
31794
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
31795
|
+
result[serverName] = omitPrototypePollutionKeys(serverConfig);
|
|
31796
|
+
}
|
|
31797
|
+
return result;
|
|
31798
|
+
}
|
|
31799
|
+
/**
|
|
31800
|
+
* Continue MCP configuration.
|
|
31801
|
+
*
|
|
31802
|
+
* Continue loads every `*.json` file under `.continue/mcpServers/` (project)
|
|
31803
|
+
* and `~/.continue/mcpServers/` (global); rulesync owns one of them,
|
|
31804
|
+
* `mcp.json`, in the `{ "mcpServers": { ... } }` shape the docs show. Other
|
|
31805
|
+
* files in the directory, and top-level sibling keys of `mcp.json`, are left
|
|
31806
|
+
* alone. The global file is not deleted by `--delete` (it lives outside the
|
|
31807
|
+
* project), while the project file is rulesync's own and is.
|
|
31808
|
+
*
|
|
31809
|
+
* @see https://docs.continue.dev/customize/deep-dives/mcp
|
|
31810
|
+
*/
|
|
31811
|
+
var ContinueMcp = class ContinueMcp extends ToolMcp {
|
|
31812
|
+
json;
|
|
31813
|
+
constructor(params) {
|
|
31814
|
+
super(params);
|
|
31815
|
+
this.json = this.fileContent === void 0 ? {} : parseContinueMcpConfig({
|
|
31816
|
+
fileContent: this.fileContent,
|
|
31817
|
+
relativePath: (0, node_path.join)(this.relativeDirPath, this.relativeFilePath)
|
|
31818
|
+
});
|
|
31819
|
+
}
|
|
31820
|
+
getJson() {
|
|
31821
|
+
return this.json;
|
|
31822
|
+
}
|
|
31823
|
+
isDeletable() {
|
|
31824
|
+
return !this.global;
|
|
31825
|
+
}
|
|
31826
|
+
static getSettablePaths(_options = {}) {
|
|
31827
|
+
return {
|
|
31828
|
+
relativeDirPath: CONTINUE_MCP_DIR_PATH,
|
|
31829
|
+
relativeFilePath: CONTINUE_MCP_FILE_NAME
|
|
31830
|
+
};
|
|
31831
|
+
}
|
|
31832
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
31833
|
+
const paths = this.getSettablePaths({ global });
|
|
31834
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
|
|
31835
|
+
return new ContinueMcp({
|
|
31836
|
+
outputRoot,
|
|
31837
|
+
relativeDirPath: paths.relativeDirPath,
|
|
31838
|
+
relativeFilePath: paths.relativeFilePath,
|
|
31839
|
+
fileContent,
|
|
31840
|
+
validate,
|
|
31841
|
+
global
|
|
31842
|
+
});
|
|
31843
|
+
}
|
|
31844
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
31845
|
+
const paths = this.getSettablePaths({ global });
|
|
31846
|
+
const json = parseContinueMcpConfig({
|
|
31847
|
+
fileContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
|
|
31848
|
+
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
31849
|
+
});
|
|
31850
|
+
const mcpServers = convertToContinueFormat(rulesyncMcp.getMcpServers(), logger);
|
|
31851
|
+
const continueConfig = {
|
|
31852
|
+
...json,
|
|
31853
|
+
mcpServers
|
|
31854
|
+
};
|
|
31855
|
+
return new ContinueMcp({
|
|
31856
|
+
outputRoot,
|
|
31857
|
+
relativeDirPath: paths.relativeDirPath,
|
|
31858
|
+
relativeFilePath: paths.relativeFilePath,
|
|
31859
|
+
fileContent: JSON.stringify(continueConfig, null, 2),
|
|
31860
|
+
validate,
|
|
31861
|
+
global
|
|
31862
|
+
});
|
|
31863
|
+
}
|
|
31864
|
+
toRulesyncMcp() {
|
|
31865
|
+
const mcpServers = convertFromContinueFormat(this.json.mcpServers);
|
|
31866
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
31867
|
+
}
|
|
31868
|
+
validate() {
|
|
31869
|
+
return {
|
|
31870
|
+
success: true,
|
|
31871
|
+
error: null
|
|
31872
|
+
};
|
|
31873
|
+
}
|
|
31874
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
31875
|
+
return new ContinueMcp({
|
|
31876
|
+
outputRoot,
|
|
31877
|
+
relativeDirPath,
|
|
31878
|
+
relativeFilePath,
|
|
31879
|
+
fileContent: "{}",
|
|
31880
|
+
validate: false,
|
|
31881
|
+
global
|
|
31882
|
+
});
|
|
31883
|
+
}
|
|
31884
|
+
};
|
|
31885
|
+
//#endregion
|
|
31188
31886
|
//#region src/features/mcp/copilot-mcp.ts
|
|
31189
31887
|
function convertFromCopilotFormat(copilotConfig) {
|
|
31190
31888
|
return copilotConfig.servers ?? {};
|
|
@@ -36917,6 +37615,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
36917
37615
|
supportsDisabledTools: true
|
|
36918
37616
|
}
|
|
36919
37617
|
}],
|
|
37618
|
+
["continue", {
|
|
37619
|
+
class: ContinueMcp,
|
|
37620
|
+
meta: {
|
|
37621
|
+
supportsProject: true,
|
|
37622
|
+
supportsGlobal: true,
|
|
37623
|
+
supportsEnabledTools: false,
|
|
37624
|
+
supportsDisabledTools: false
|
|
37625
|
+
}
|
|
37626
|
+
}],
|
|
36920
37627
|
["copilot", {
|
|
36921
37628
|
class: CopilotMcp,
|
|
36922
37629
|
meta: {
|
|
@@ -38375,7 +39082,7 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
|
|
|
38375
39082
|
function toAntigravityCliToolName(canonical) {
|
|
38376
39083
|
return CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES[canonical] ?? canonical;
|
|
38377
39084
|
}
|
|
38378
|
-
function toCanonicalToolName$
|
|
39085
|
+
function toCanonicalToolName$7(cliName) {
|
|
38379
39086
|
return ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES[cliName] ?? cliName;
|
|
38380
39087
|
}
|
|
38381
39088
|
/**
|
|
@@ -38591,7 +39298,7 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
38591
39298
|
const processEntries = (entries, action) => {
|
|
38592
39299
|
for (const entry of entries) {
|
|
38593
39300
|
const { toolName, pattern } = parsePermissionEntry$1(entry);
|
|
38594
|
-
const canonical = toCanonicalToolName$
|
|
39301
|
+
const canonical = toCanonicalToolName$7(toolName);
|
|
38595
39302
|
if (!permission[canonical]) permission[canonical] = {};
|
|
38596
39303
|
permission[canonical][pattern] = action;
|
|
38597
39304
|
}
|
|
@@ -38851,7 +39558,7 @@ const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONI
|
|
|
38851
39558
|
function toAugmentToolName(canonical) {
|
|
38852
39559
|
return CANONICAL_TO_AUGMENT_TOOL_NAMES[canonical] ?? canonical;
|
|
38853
39560
|
}
|
|
38854
|
-
function toCanonicalToolName$
|
|
39561
|
+
function toCanonicalToolName$6(augmentName) {
|
|
38855
39562
|
return AUGMENT_TO_CANONICAL_TOOL_NAMES[augmentName] ?? augmentName;
|
|
38856
39563
|
}
|
|
38857
39564
|
function actionToAugmentType(action) {
|
|
@@ -39298,7 +40005,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
39298
40005
|
if (isSpecialEntry(entry)) continue;
|
|
39299
40006
|
const type = entry.permission.type;
|
|
39300
40007
|
if (!isBasicAugmentType(type)) continue;
|
|
39301
|
-
const canonical = toCanonicalToolName$
|
|
40008
|
+
const canonical = toCanonicalToolName$6(entry.toolName);
|
|
39302
40009
|
if (forbiddenMapKeys.has(canonical)) {
|
|
39303
40010
|
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
40011
|
continue;
|
|
@@ -39533,7 +40240,7 @@ const CLAUDE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONIC
|
|
|
39533
40240
|
function toClaudeToolName(canonical) {
|
|
39534
40241
|
return CANONICAL_TO_CLAUDE_TOOL_NAMES[canonical] ?? canonical;
|
|
39535
40242
|
}
|
|
39536
|
-
function toCanonicalToolName$
|
|
40243
|
+
function toCanonicalToolName$5(claudeName) {
|
|
39537
40244
|
return CLAUDE_TO_CANONICAL_TOOL_NAMES[claudeName] ?? claudeName;
|
|
39538
40245
|
}
|
|
39539
40246
|
/**
|
|
@@ -40428,7 +41135,7 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
40428
41135
|
const processEntries = (entries, action) => {
|
|
40429
41136
|
for (const entry of entries) {
|
|
40430
41137
|
const { toolName, pattern } = parseClaudePermissionEntry(entry);
|
|
40431
|
-
const canonical = toCanonicalToolName$
|
|
41138
|
+
const canonical = toCanonicalToolName$5(toolName);
|
|
40432
41139
|
if (!permission[canonical]) permission[canonical] = {};
|
|
40433
41140
|
permission[canonical][pattern] = action;
|
|
40434
41141
|
}
|
|
@@ -41251,6 +41958,264 @@ function mapBashActionToDecision(action) {
|
|
|
41251
41958
|
return "forbidden";
|
|
41252
41959
|
}
|
|
41253
41960
|
//#endregion
|
|
41961
|
+
//#region src/features/permissions/continue-permissions.ts
|
|
41962
|
+
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
41963
|
+
const CATCH_ALL_PATTERN$4 = "*";
|
|
41964
|
+
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
41965
|
+
bash: "Bash",
|
|
41966
|
+
read: "Read",
|
|
41967
|
+
edit: "Edit",
|
|
41968
|
+
write: "Write",
|
|
41969
|
+
webfetch: "Fetch"
|
|
41970
|
+
};
|
|
41971
|
+
const CONTINUE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CONTINUE_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
41972
|
+
const ACTION_TO_CONTINUE_LIST = {
|
|
41973
|
+
allow: "allow",
|
|
41974
|
+
ask: "ask",
|
|
41975
|
+
deny: "exclude"
|
|
41976
|
+
};
|
|
41977
|
+
const CONTINUE_LIST_TO_ACTION = {
|
|
41978
|
+
allow: "allow",
|
|
41979
|
+
ask: "ask",
|
|
41980
|
+
exclude: "deny"
|
|
41981
|
+
};
|
|
41982
|
+
const CONTINUE_PERMISSION_LIST_KEYS = [
|
|
41983
|
+
"exclude",
|
|
41984
|
+
"ask",
|
|
41985
|
+
"allow"
|
|
41986
|
+
];
|
|
41987
|
+
const CONTINUE_PATTERN_RE = /^([^(]+)(?:\(([^)]*)\))?$/;
|
|
41988
|
+
function toContinueToolName(canonical) {
|
|
41989
|
+
return Object.hasOwn(CANONICAL_TO_CONTINUE_TOOL_NAMES, canonical) ? CANONICAL_TO_CONTINUE_TOOL_NAMES[canonical] ?? canonical : canonical;
|
|
41990
|
+
}
|
|
41991
|
+
function toCanonicalToolName$4(continueName) {
|
|
41992
|
+
return Object.hasOwn(CONTINUE_TO_CANONICAL_TOOL_NAMES, continueName) ? CONTINUE_TO_CANONICAL_TOOL_NAMES[continueName] ?? continueName : continueName;
|
|
41993
|
+
}
|
|
41994
|
+
/**
|
|
41995
|
+
* Build a permissions.yaml entry: the bare tool name for the catch-all, else
|
|
41996
|
+
* `Tool(pattern)`.
|
|
41997
|
+
*/
|
|
41998
|
+
function buildContinuePermissionEntry(toolName, pattern) {
|
|
41999
|
+
return pattern === CATCH_ALL_PATTERN$4 ? toolName : `${toolName}(${pattern})`;
|
|
42000
|
+
}
|
|
42001
|
+
/**
|
|
42002
|
+
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
42003
|
+
* `undefined` for an entry outside the loader's grammar.
|
|
42004
|
+
*/
|
|
42005
|
+
function parseContinuePermissionEntry(entry) {
|
|
42006
|
+
const match = CONTINUE_PATTERN_RE.exec(entry);
|
|
42007
|
+
if (!match) return;
|
|
42008
|
+
const toolName = match[1]?.trim() ?? "";
|
|
42009
|
+
const pattern = match[2]?.trim() ?? "";
|
|
42010
|
+
if (toolName === "") return;
|
|
42011
|
+
return {
|
|
42012
|
+
toolName,
|
|
42013
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$4 : pattern
|
|
42014
|
+
};
|
|
42015
|
+
}
|
|
42016
|
+
/**
|
|
42017
|
+
* Permissions adapter for the Continue CLI (`cn`).
|
|
42018
|
+
*
|
|
42019
|
+
* Continue reads tool policies from `~/.continue/permissions.yaml`, three
|
|
42020
|
+
* string lists (`allow`, `ask`, `exclude`) of `Tool` / `Tool(pattern)` entries
|
|
42021
|
+
* evaluated exclude-first, then ask, then allow, first match wins. `exclude`
|
|
42022
|
+
* disables the tool. A `Tool(pattern)` entry glob-matches (`*` / `?`) the
|
|
42023
|
+
* tool's primary argument — the shell command for `Bash`, the path for
|
|
42024
|
+
* `Read` / `Edit` / `Write`, the URL for `Fetch`. The file is **global only**:
|
|
42025
|
+
* no project-scoped permissions file is read.
|
|
42026
|
+
*
|
|
42027
|
+
* Mapping (rulesync canonical -> Continue):
|
|
42028
|
+
* - Action: `allow` -> `allow`, `ask` -> `ask`, `deny` -> `exclude`.
|
|
42029
|
+
* - Tool name: `bash` -> `Bash`, `read` -> `Read`, `edit` -> `Edit`,
|
|
42030
|
+
* `write` -> `Write`, `webfetch` -> `Fetch`; any other category passes
|
|
42031
|
+
* through verbatim, and the all-tools `*` category becomes the bare `*`
|
|
42032
|
+
* entry that matches every tool.
|
|
42033
|
+
* - Entry: the catch-all `*` pattern is the bare tool name; every other
|
|
42034
|
+
* pattern is written as `Tool(pattern)`. A pattern holding a parenthesis
|
|
42035
|
+
* cannot be written (the loader would refuse the whole file) and is
|
|
42036
|
+
* reported and skipped.
|
|
42037
|
+
*
|
|
42038
|
+
* The CLI appends the user's own "always allow" decisions to the same file,
|
|
42039
|
+
* so entries naming a tool the canonical config does not manage are preserved
|
|
42040
|
+
* verbatim, the managed tools' entries are rebuilt, and the file is never
|
|
42041
|
+
* deleted.
|
|
42042
|
+
*
|
|
42043
|
+
* @see https://docs.continue.dev/cli/tool-permissions
|
|
42044
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/permissions/permissionsYamlLoader.ts
|
|
42045
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/permissions/permissionChecker.ts
|
|
42046
|
+
*/
|
|
42047
|
+
var ContinuePermissions = class ContinuePermissions extends ToolPermissions {
|
|
42048
|
+
constructor(params) {
|
|
42049
|
+
super({
|
|
42050
|
+
...params,
|
|
42051
|
+
fileContent: params.fileContent ?? ""
|
|
42052
|
+
});
|
|
42053
|
+
}
|
|
42054
|
+
isDeletable() {
|
|
42055
|
+
return false;
|
|
42056
|
+
}
|
|
42057
|
+
/**
|
|
42058
|
+
* `permissions.yaml` is Continue's file: rulesync merges into it when it
|
|
42059
|
+
* exists but does not create one that holds three empty lists — an absent
|
|
42060
|
+
* file and empty lists both mean "no policy, ask for everything".
|
|
42061
|
+
*/
|
|
42062
|
+
shouldSkipCreationWhenPayloadEmpty() {
|
|
42063
|
+
return true;
|
|
42064
|
+
}
|
|
42065
|
+
static getSettablePaths(_options) {
|
|
42066
|
+
return {
|
|
42067
|
+
relativeDirPath: CONTINUE_DIR,
|
|
42068
|
+
relativeFilePath: CONTINUE_PERMISSIONS_FILE_NAME
|
|
42069
|
+
};
|
|
42070
|
+
}
|
|
42071
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
42072
|
+
if (!global) throw new Error(CONTINUE_GLOBAL_ONLY_MESSAGE);
|
|
42073
|
+
const paths = ContinuePermissions.getSettablePaths({ global });
|
|
42074
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
42075
|
+
return new ContinuePermissions({
|
|
42076
|
+
outputRoot,
|
|
42077
|
+
relativeDirPath: paths.relativeDirPath,
|
|
42078
|
+
relativeFilePath: paths.relativeFilePath,
|
|
42079
|
+
fileContent,
|
|
42080
|
+
validate,
|
|
42081
|
+
global: true
|
|
42082
|
+
});
|
|
42083
|
+
}
|
|
42084
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
42085
|
+
if (!global) throw new Error(CONTINUE_GLOBAL_ONLY_MESSAGE);
|
|
42086
|
+
const paths = ContinuePermissions.getSettablePaths({ global });
|
|
42087
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
42088
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
42089
|
+
let parsed;
|
|
42090
|
+
try {
|
|
42091
|
+
parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
|
|
42092
|
+
} catch (error) {
|
|
42093
|
+
throw new Error(`Failed to parse existing Continue permissions.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
42094
|
+
}
|
|
42095
|
+
const existing = parseContinuePermissionLists(isRecord$1(parsed) ? parsed : {});
|
|
42096
|
+
const config = rulesyncPermissions.getJson();
|
|
42097
|
+
const generated = convertRulesyncToContinuePermissions({
|
|
42098
|
+
config,
|
|
42099
|
+
logger
|
|
42100
|
+
});
|
|
42101
|
+
const managedToolNames = managedContinueToolNames(config);
|
|
42102
|
+
const lists = {};
|
|
42103
|
+
for (const key of CONTINUE_PERMISSION_LIST_KEYS) {
|
|
42104
|
+
const preserved = existing[key].filter((entry) => {
|
|
42105
|
+
const parsedEntry = parseContinuePermissionEntry(entry);
|
|
42106
|
+
return parsedEntry === void 0 || !managedToolNames.has(parsedEntry.toolName);
|
|
42107
|
+
});
|
|
42108
|
+
const merged = [.../* @__PURE__ */ new Set([...preserved, ...generated[key]])];
|
|
42109
|
+
if (merged.length > 0) lists[key] = merged;
|
|
42110
|
+
}
|
|
42111
|
+
return new ContinuePermissions({
|
|
42112
|
+
outputRoot,
|
|
42113
|
+
relativeDirPath: paths.relativeDirPath,
|
|
42114
|
+
relativeFilePath: paths.relativeFilePath,
|
|
42115
|
+
fileContent: Object.keys(lists).length === 0 ? "" : (0, js_yaml.dump)(lists),
|
|
42116
|
+
validate: true,
|
|
42117
|
+
global: true
|
|
42118
|
+
});
|
|
42119
|
+
}
|
|
42120
|
+
toRulesyncPermissions() {
|
|
42121
|
+
let parsed;
|
|
42122
|
+
try {
|
|
42123
|
+
const content = this.getFileContent();
|
|
42124
|
+
parsed = content.trim() === "" ? {} : loadYaml(content);
|
|
42125
|
+
} catch (error) {
|
|
42126
|
+
throw new Error(`Failed to parse Continue permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
42127
|
+
}
|
|
42128
|
+
const rulesyncConfig = convertContinuePermissionsToRulesync(parseContinuePermissionLists(isRecord$1(parsed) ? parsed : {}));
|
|
42129
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
42130
|
+
}
|
|
42131
|
+
validate() {
|
|
42132
|
+
return {
|
|
42133
|
+
success: true,
|
|
42134
|
+
error: null
|
|
42135
|
+
};
|
|
42136
|
+
}
|
|
42137
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
42138
|
+
return new ContinuePermissions({
|
|
42139
|
+
outputRoot,
|
|
42140
|
+
relativeDirPath,
|
|
42141
|
+
relativeFilePath,
|
|
42142
|
+
fileContent: "",
|
|
42143
|
+
validate: false,
|
|
42144
|
+
global: true
|
|
42145
|
+
});
|
|
42146
|
+
}
|
|
42147
|
+
};
|
|
42148
|
+
/**
|
|
42149
|
+
* The Continue tool names the canonical config manages, i.e. the names its
|
|
42150
|
+
* categories map to. Entries naming any other tool are the user's (or the
|
|
42151
|
+
* CLI's) and survive a generate.
|
|
42152
|
+
*/
|
|
42153
|
+
function managedContinueToolNames(config) {
|
|
42154
|
+
return new Set(Object.keys(config.permission).map((category) => toContinueToolName(category)));
|
|
42155
|
+
}
|
|
42156
|
+
/**
|
|
42157
|
+
* Read the three lists out of a parsed permissions.yaml, tolerating a missing
|
|
42158
|
+
* or malformed list (the CLI itself discards the file in that case).
|
|
42159
|
+
*/
|
|
42160
|
+
function parseContinuePermissionLists(parsed) {
|
|
42161
|
+
return {
|
|
42162
|
+
exclude: isStringArray$2(parsed.exclude) ? parsed.exclude : [],
|
|
42163
|
+
ask: isStringArray$2(parsed.ask) ? parsed.ask : [],
|
|
42164
|
+
allow: isStringArray$2(parsed.allow) ? parsed.allow : []
|
|
42165
|
+
};
|
|
42166
|
+
}
|
|
42167
|
+
/**
|
|
42168
|
+
* Convert a rulesync permissions config into the three permissions.yaml lists.
|
|
42169
|
+
*/
|
|
42170
|
+
function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
42171
|
+
const lists = {
|
|
42172
|
+
exclude: [],
|
|
42173
|
+
ask: [],
|
|
42174
|
+
allow: []
|
|
42175
|
+
};
|
|
42176
|
+
const actionByEntry = /* @__PURE__ */ new Map();
|
|
42177
|
+
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
42178
|
+
if (isPrototypePollutionKey(category)) continue;
|
|
42179
|
+
const toolName = category === "*" ? "*" : toContinueToolName(category);
|
|
42180
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
42181
|
+
if (isPrototypePollutionKey(pattern)) continue;
|
|
42182
|
+
if (pattern.includes("(") || pattern.includes(")")) {
|
|
42183
|
+
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
42184
|
+
continue;
|
|
42185
|
+
}
|
|
42186
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$4) {
|
|
42187
|
+
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
42188
|
+
continue;
|
|
42189
|
+
}
|
|
42190
|
+
const entry = buildContinuePermissionEntry(toolName, pattern);
|
|
42191
|
+
const previous = actionByEntry.get(entry);
|
|
42192
|
+
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.`);
|
|
42193
|
+
actionByEntry.set(entry, action);
|
|
42194
|
+
lists[ACTION_TO_CONTINUE_LIST[action]].push(entry);
|
|
42195
|
+
}
|
|
42196
|
+
}
|
|
42197
|
+
for (const key of CONTINUE_PERMISSION_LIST_KEYS) lists[key] = [...new Set(lists[key])];
|
|
42198
|
+
return lists;
|
|
42199
|
+
}
|
|
42200
|
+
/**
|
|
42201
|
+
* Convert the three permissions.yaml lists back into a rulesync config.
|
|
42202
|
+
*/
|
|
42203
|
+
function convertContinuePermissionsToRulesync(lists) {
|
|
42204
|
+
const permission = {};
|
|
42205
|
+
for (const key of CONTINUE_PERMISSION_LIST_KEYS) {
|
|
42206
|
+
const action = CONTINUE_LIST_TO_ACTION[key];
|
|
42207
|
+
for (const entry of lists[key]) {
|
|
42208
|
+
const parsedEntry = parseContinuePermissionEntry(entry);
|
|
42209
|
+
if (parsedEntry === void 0) continue;
|
|
42210
|
+
if (isPrototypePollutionKey(parsedEntry.toolName) || isPrototypePollutionKey(parsedEntry.pattern)) continue;
|
|
42211
|
+
const category = toCanonicalToolName$4(parsedEntry.toolName);
|
|
42212
|
+
permission[category] ??= {};
|
|
42213
|
+
permission[category][parsedEntry.pattern] ??= action;
|
|
42214
|
+
}
|
|
42215
|
+
}
|
|
42216
|
+
return { permission };
|
|
42217
|
+
}
|
|
42218
|
+
//#endregion
|
|
41254
42219
|
//#region src/features/permissions/copilot-permissions.ts
|
|
41255
42220
|
/**
|
|
41256
42221
|
* The flat, dotted VS Code setting keys this adapter manages, one per canonical
|
|
@@ -51299,6 +52264,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
51299
52264
|
supportsImport: true
|
|
51300
52265
|
}
|
|
51301
52266
|
}],
|
|
52267
|
+
["continue", {
|
|
52268
|
+
class: ContinuePermissions,
|
|
52269
|
+
meta: {
|
|
52270
|
+
supportsProject: false,
|
|
52271
|
+
supportsGlobal: true,
|
|
52272
|
+
supportsImport: true
|
|
52273
|
+
}
|
|
52274
|
+
}],
|
|
51302
52275
|
["copilot", {
|
|
51303
52276
|
class: CopilotPermissions,
|
|
51304
52277
|
meta: {
|
|
@@ -54049,6 +55022,154 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
|
|
|
54049
55022
|
}
|
|
54050
55023
|
};
|
|
54051
55024
|
//#endregion
|
|
55025
|
+
//#region src/features/skills/continue-skill.ts
|
|
55026
|
+
const ContinueSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
55027
|
+
name: zod_mini.z.string(),
|
|
55028
|
+
description: zod_mini.z.string()
|
|
55029
|
+
});
|
|
55030
|
+
/**
|
|
55031
|
+
* Represents a Continue skill directory.
|
|
55032
|
+
*
|
|
55033
|
+
* Continue discovers Anthropic-style `<name>/SKILL.md` directories under
|
|
55034
|
+
* `<project>/.continue/skills/` (project scope) and `~/.continue/skills/`
|
|
55035
|
+
* (user scope); it also reads `.claude/skills/`, which the claudecode target
|
|
55036
|
+
* covers. The frontmatter requires `name` (matching the directory name) and
|
|
55037
|
+
* `description`; supporting files next to `SKILL.md` are carried along.
|
|
55038
|
+
*
|
|
55039
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/util/loadMarkdownSkills.ts
|
|
55040
|
+
*/
|
|
55041
|
+
var ContinueSkill = class ContinueSkill extends ToolSkill {
|
|
55042
|
+
constructor({ outputRoot = process.cwd(), relativeDirPath = CONTINUE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
55043
|
+
super({
|
|
55044
|
+
outputRoot,
|
|
55045
|
+
relativeDirPath,
|
|
55046
|
+
dirName,
|
|
55047
|
+
mainFile: {
|
|
55048
|
+
name: SKILL_FILE_NAME,
|
|
55049
|
+
body,
|
|
55050
|
+
frontmatter: { ...frontmatter }
|
|
55051
|
+
},
|
|
55052
|
+
otherFiles,
|
|
55053
|
+
global
|
|
55054
|
+
});
|
|
55055
|
+
if (validate) {
|
|
55056
|
+
const result = this.validate();
|
|
55057
|
+
if (!result.success) throw result.error;
|
|
55058
|
+
}
|
|
55059
|
+
}
|
|
55060
|
+
static getSettablePaths(_options = {}) {
|
|
55061
|
+
return { relativeDirPath: CONTINUE_SKILLS_DIR_PATH };
|
|
55062
|
+
}
|
|
55063
|
+
getFrontmatter() {
|
|
55064
|
+
return ContinueSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
55065
|
+
}
|
|
55066
|
+
getBody() {
|
|
55067
|
+
return this.mainFile?.body ?? "";
|
|
55068
|
+
}
|
|
55069
|
+
validate() {
|
|
55070
|
+
if (!this.mainFile) return {
|
|
55071
|
+
success: false,
|
|
55072
|
+
error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
|
|
55073
|
+
};
|
|
55074
|
+
const result = ContinueSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
|
|
55075
|
+
if (!result.success) return {
|
|
55076
|
+
success: false,
|
|
55077
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
|
|
55078
|
+
};
|
|
55079
|
+
if (result.data.name !== this.getDirName()) return {
|
|
55080
|
+
success: false,
|
|
55081
|
+
error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
|
|
55082
|
+
};
|
|
55083
|
+
return {
|
|
55084
|
+
success: true,
|
|
55085
|
+
error: null
|
|
55086
|
+
};
|
|
55087
|
+
}
|
|
55088
|
+
toRulesyncSkill() {
|
|
55089
|
+
const { name, description, ...continueSection } = this.getFrontmatter();
|
|
55090
|
+
const rulesyncFrontmatter = {
|
|
55091
|
+
name,
|
|
55092
|
+
description,
|
|
55093
|
+
targets: ["*"],
|
|
55094
|
+
...Object.keys(continueSection).length > 0 && { continue: continueSection }
|
|
55095
|
+
};
|
|
55096
|
+
return new RulesyncSkill({
|
|
55097
|
+
outputRoot: this.outputRoot,
|
|
55098
|
+
relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
|
|
55099
|
+
dirName: this.getDirName(),
|
|
55100
|
+
frontmatter: rulesyncFrontmatter,
|
|
55101
|
+
body: this.getBody(),
|
|
55102
|
+
otherFiles: this.getOtherFiles(),
|
|
55103
|
+
validate: true,
|
|
55104
|
+
global: this.global
|
|
55105
|
+
});
|
|
55106
|
+
}
|
|
55107
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
55108
|
+
const settablePaths = ContinueSkill.getSettablePaths({ global });
|
|
55109
|
+
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
55110
|
+
const { name: _sectionName, description: _sectionDescription, ...continueSection } = rulesyncFrontmatter.continue ?? {};
|
|
55111
|
+
const continueFrontmatter = {
|
|
55112
|
+
...continueSection,
|
|
55113
|
+
name: rulesyncFrontmatter.name,
|
|
55114
|
+
description: rulesyncFrontmatter.description
|
|
55115
|
+
};
|
|
55116
|
+
return new ContinueSkill({
|
|
55117
|
+
outputRoot,
|
|
55118
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
55119
|
+
dirName: continueFrontmatter.name,
|
|
55120
|
+
frontmatter: continueFrontmatter,
|
|
55121
|
+
body: rulesyncSkill.getBody(),
|
|
55122
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
55123
|
+
validate,
|
|
55124
|
+
global
|
|
55125
|
+
});
|
|
55126
|
+
}
|
|
55127
|
+
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
55128
|
+
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
55129
|
+
return targets.includes("*") || targets.includes("continue");
|
|
55130
|
+
}
|
|
55131
|
+
static async fromDir(params) {
|
|
55132
|
+
const loaded = await this.loadSkillDirContent({
|
|
55133
|
+
...params,
|
|
55134
|
+
getSettablePaths: ContinueSkill.getSettablePaths
|
|
55135
|
+
});
|
|
55136
|
+
const result = ContinueSkillFrontmatterSchema.safeParse(loaded.frontmatter);
|
|
55137
|
+
if (!result.success) {
|
|
55138
|
+
const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
55139
|
+
throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
55140
|
+
}
|
|
55141
|
+
if (result.data.name !== loaded.dirName) {
|
|
55142
|
+
const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
|
|
55143
|
+
throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
|
|
55144
|
+
}
|
|
55145
|
+
return new ContinueSkill({
|
|
55146
|
+
outputRoot: loaded.outputRoot,
|
|
55147
|
+
relativeDirPath: loaded.relativeDirPath,
|
|
55148
|
+
dirName: loaded.dirName,
|
|
55149
|
+
frontmatter: result.data,
|
|
55150
|
+
body: loaded.body,
|
|
55151
|
+
otherFiles: loaded.otherFiles,
|
|
55152
|
+
validate: true,
|
|
55153
|
+
global: loaded.global
|
|
55154
|
+
});
|
|
55155
|
+
}
|
|
55156
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
|
|
55157
|
+
return new ContinueSkill({
|
|
55158
|
+
outputRoot,
|
|
55159
|
+
relativeDirPath,
|
|
55160
|
+
dirName,
|
|
55161
|
+
frontmatter: {
|
|
55162
|
+
name: "",
|
|
55163
|
+
description: ""
|
|
55164
|
+
},
|
|
55165
|
+
body: "",
|
|
55166
|
+
otherFiles: [],
|
|
55167
|
+
validate: false,
|
|
55168
|
+
global
|
|
55169
|
+
});
|
|
55170
|
+
}
|
|
55171
|
+
};
|
|
55172
|
+
//#endregion
|
|
54052
55173
|
//#region src/features/skills/copilot-skill.ts
|
|
54053
55174
|
const CopilotSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
54054
55175
|
name: zod_mini.z.string(),
|
|
@@ -58913,6 +60034,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
58913
60034
|
supportsGlobal: true
|
|
58914
60035
|
}
|
|
58915
60036
|
}],
|
|
60037
|
+
["continue", {
|
|
60038
|
+
class: ContinueSkill,
|
|
60039
|
+
meta: {
|
|
60040
|
+
supportsProject: true,
|
|
60041
|
+
supportsSimulated: false,
|
|
60042
|
+
supportsGlobal: true
|
|
60043
|
+
}
|
|
60044
|
+
}],
|
|
58916
60045
|
["copilot", {
|
|
58917
60046
|
class: CopilotSkill,
|
|
58918
60047
|
meta: {
|
|
@@ -67481,6 +68610,263 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
67481
68610
|
}
|
|
67482
68611
|
};
|
|
67483
68612
|
//#endregion
|
|
68613
|
+
//#region src/features/rules/continue-rule.ts
|
|
68614
|
+
/**
|
|
68615
|
+
* Frontmatter schema for Continue rule files (`.continue/rules/*.md`).
|
|
68616
|
+
* `globs` and `regex` are documented as either a single pattern or a list.
|
|
68617
|
+
* @see https://docs.continue.dev/customize/deep-dives/rules
|
|
68618
|
+
*/
|
|
68619
|
+
const ContinueRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
68620
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
68621
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
68622
|
+
globs: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
68623
|
+
regex: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
68624
|
+
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean())
|
|
68625
|
+
});
|
|
68626
|
+
/**
|
|
68627
|
+
* Normalizes the documented `string` / `string[]` shapes to the list form the
|
|
68628
|
+
* adapter works with. Continue treats a scalar as one pattern (no comma
|
|
68629
|
+
* splitting), so the scalar simply becomes a one-element list. An empty list
|
|
68630
|
+
* and an empty string both mean "not set".
|
|
68631
|
+
*/
|
|
68632
|
+
function normalizePatternList(value) {
|
|
68633
|
+
if (value === void 0) return;
|
|
68634
|
+
const list = (typeof value === "string" ? [value] : value).filter((pattern) => pattern.trim() !== "");
|
|
68635
|
+
return list.length > 0 ? list : void 0;
|
|
68636
|
+
}
|
|
68637
|
+
/**
|
|
68638
|
+
* A universal glob is redundant on a Continue rule: a `.continue/rules/` file
|
|
68639
|
+
* with no `globs` is already implicitly always-on, and one with `globs` is
|
|
68640
|
+
* only pulled in when a context file matches, so emitting `**\/*` would turn
|
|
68641
|
+
* an always-on rule into a "when any file is in context" rule. Dropped on
|
|
68642
|
+
* generate and mapped back on import.
|
|
68643
|
+
*/
|
|
68644
|
+
const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
68645
|
+
/**
|
|
68646
|
+
* Rule generator for Continue (continuedev/continue), the open-source IDE
|
|
68647
|
+
* extension and CLI (`cn`).
|
|
68648
|
+
*
|
|
68649
|
+
* Rules format:
|
|
68650
|
+
* - {project}/AGENTS.md (root: true) — read from the workspace root and
|
|
68651
|
+
* always applied.
|
|
68652
|
+
* - {project}/.continue/rules/*.md (root: false) — Markdown with optional
|
|
68653
|
+
* `description` / `globs` / `regex` / `alwaysApply` frontmatter. A file
|
|
68654
|
+
* with no `globs`/`regex` is always applied; one with `globs` is applied
|
|
68655
|
+
* when a context file matches; `alwaysApply: true` forces it on and
|
|
68656
|
+
* `alwaysApply: false` makes it depend on `globs`/`regex` only.
|
|
68657
|
+
* - Global: ~/.continue/rules/*.md. Continue does not read `~/AGENTS.md`, so
|
|
68658
|
+
* the global root rule is written to `~/.continue/rules/AGENTS.md`, the
|
|
68659
|
+
* same layout the Roo adapter uses.
|
|
68660
|
+
*
|
|
68661
|
+
* @see https://docs.continue.dev/customize/deep-dives/rules
|
|
68662
|
+
*/
|
|
68663
|
+
var ContinueRule = class ContinueRule extends ToolRule {
|
|
68664
|
+
frontmatter;
|
|
68665
|
+
body;
|
|
68666
|
+
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
68667
|
+
const rulesDirPath = buildToolPath(CONTINUE_DIR, "rules", excludeToolDir);
|
|
68668
|
+
if (global) return {
|
|
68669
|
+
root: {
|
|
68670
|
+
relativeDirPath: rulesDirPath,
|
|
68671
|
+
relativeFilePath: CONTINUE_ROOT_RULE_FILE_NAME
|
|
68672
|
+
},
|
|
68673
|
+
nonRoot: { relativeDirPath: rulesDirPath }
|
|
68674
|
+
};
|
|
68675
|
+
return {
|
|
68676
|
+
root: {
|
|
68677
|
+
relativeDirPath: ".",
|
|
68678
|
+
relativeFilePath: CONTINUE_ROOT_RULE_FILE_NAME
|
|
68679
|
+
},
|
|
68680
|
+
nonRoot: { relativeDirPath: rulesDirPath }
|
|
68681
|
+
};
|
|
68682
|
+
}
|
|
68683
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
68684
|
+
if (rest.validate) {
|
|
68685
|
+
const result = ContinueRuleFrontmatterSchema.safeParse(frontmatter);
|
|
68686
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
68687
|
+
}
|
|
68688
|
+
super({
|
|
68689
|
+
...rest,
|
|
68690
|
+
fileContent: rest.root ? body : ContinueRule.generateFileContent(body, frontmatter)
|
|
68691
|
+
});
|
|
68692
|
+
this.frontmatter = frontmatter;
|
|
68693
|
+
this.body = body;
|
|
68694
|
+
}
|
|
68695
|
+
static generateFileContent(body, frontmatter) {
|
|
68696
|
+
if (frontmatter.name === void 0 && frontmatter.description === void 0 && frontmatter.globs === void 0 && frontmatter.regex === void 0 && frontmatter.alwaysApply === void 0) return body;
|
|
68697
|
+
return stringifyFrontmatter(body, {
|
|
68698
|
+
name: frontmatter.name,
|
|
68699
|
+
description: frontmatter.description,
|
|
68700
|
+
globs: frontmatter.globs,
|
|
68701
|
+
regex: frontmatter.regex,
|
|
68702
|
+
alwaysApply: frontmatter.alwaysApply
|
|
68703
|
+
});
|
|
68704
|
+
}
|
|
68705
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false, relativeDirPath: overrideDirPath }) {
|
|
68706
|
+
const paths = this.getSettablePaths({ global });
|
|
68707
|
+
const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
|
|
68708
|
+
if (relativeFilePath === paths.root.relativeFilePath && rootDirPath === paths.root.relativeDirPath) {
|
|
68709
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootDirPath, paths.root.relativeFilePath));
|
|
68710
|
+
return new ContinueRule({
|
|
68711
|
+
outputRoot,
|
|
68712
|
+
relativeDirPath: rootDirPath,
|
|
68713
|
+
relativeFilePath: paths.root.relativeFilePath,
|
|
68714
|
+
frontmatter: {},
|
|
68715
|
+
body: fileContent.trim(),
|
|
68716
|
+
validate,
|
|
68717
|
+
root: true
|
|
68718
|
+
});
|
|
68719
|
+
}
|
|
68720
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
68721
|
+
const filePath = (0, node_path.join)(outputRoot, relativePath);
|
|
68722
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
68723
|
+
const result = ContinueRuleFrontmatterSchema.safeParse(frontmatter);
|
|
68724
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
68725
|
+
return new ContinueRule({
|
|
68726
|
+
outputRoot,
|
|
68727
|
+
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
68728
|
+
relativeFilePath,
|
|
68729
|
+
frontmatter: result.data,
|
|
68730
|
+
body: content.trim(),
|
|
68731
|
+
validate,
|
|
68732
|
+
root: false
|
|
68733
|
+
});
|
|
68734
|
+
}
|
|
68735
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
68736
|
+
const paths = this.getSettablePaths({ global });
|
|
68737
|
+
const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath === paths.root.relativeDirPath;
|
|
68738
|
+
return new ContinueRule({
|
|
68739
|
+
outputRoot,
|
|
68740
|
+
relativeDirPath,
|
|
68741
|
+
relativeFilePath,
|
|
68742
|
+
frontmatter: {},
|
|
68743
|
+
body: "",
|
|
68744
|
+
validate: false,
|
|
68745
|
+
root: isRoot
|
|
68746
|
+
});
|
|
68747
|
+
}
|
|
68748
|
+
/**
|
|
68749
|
+
* Resolves the `globs` to emit. A universal glob is dropped because a rule
|
|
68750
|
+
* without `globs` is already always-on in Continue (see `UNIVERSAL_GLOBS`),
|
|
68751
|
+
* and every glob is dropped alongside `alwaysApply: true`, which forces the
|
|
68752
|
+
* rule on regardless of them.
|
|
68753
|
+
*/
|
|
68754
|
+
static resolveContinueGlobs({ continueGlobs, parentGlobs, alwaysApply }) {
|
|
68755
|
+
if (alwaysApply === true) return;
|
|
68756
|
+
const targetGlobs = continueGlobs ?? parentGlobs;
|
|
68757
|
+
if (!targetGlobs || targetGlobs.length === 0) return;
|
|
68758
|
+
if (targetGlobs.every((glob) => UNIVERSAL_GLOBS$1.has(glob.trim()))) return;
|
|
68759
|
+
return targetGlobs;
|
|
68760
|
+
}
|
|
68761
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
68762
|
+
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
68763
|
+
const root = rulesyncFrontmatter.root ?? false;
|
|
68764
|
+
const paths = this.getSettablePaths({ global });
|
|
68765
|
+
const body = rulesyncRule.getBody();
|
|
68766
|
+
if (root) return new ContinueRule({
|
|
68767
|
+
outputRoot,
|
|
68768
|
+
frontmatter: {},
|
|
68769
|
+
body,
|
|
68770
|
+
relativeDirPath: paths.root.relativeDirPath,
|
|
68771
|
+
relativeFilePath: paths.root.relativeFilePath,
|
|
68772
|
+
validate,
|
|
68773
|
+
root
|
|
68774
|
+
});
|
|
68775
|
+
const continueFrontmatter = rulesyncFrontmatter.continue;
|
|
68776
|
+
const continueRuleFrontmatter = {
|
|
68777
|
+
name: continueFrontmatter?.name,
|
|
68778
|
+
description: continueFrontmatter?.description ?? rulesyncFrontmatter.description,
|
|
68779
|
+
globs: ContinueRule.resolveContinueGlobs({
|
|
68780
|
+
continueGlobs: normalizePatternList(continueFrontmatter?.globs),
|
|
68781
|
+
parentGlobs: rulesyncFrontmatter.globs,
|
|
68782
|
+
alwaysApply: continueFrontmatter?.alwaysApply
|
|
68783
|
+
}),
|
|
68784
|
+
regex: normalizePatternList(continueFrontmatter?.regex),
|
|
68785
|
+
alwaysApply: continueFrontmatter?.alwaysApply
|
|
68786
|
+
};
|
|
68787
|
+
return new ContinueRule({
|
|
68788
|
+
outputRoot,
|
|
68789
|
+
frontmatter: continueRuleFrontmatter,
|
|
68790
|
+
body,
|
|
68791
|
+
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
68792
|
+
relativeFilePath: rulesyncRule.getRelativeFilePath(),
|
|
68793
|
+
validate,
|
|
68794
|
+
root
|
|
68795
|
+
});
|
|
68796
|
+
}
|
|
68797
|
+
toRulesyncRule() {
|
|
68798
|
+
const targets = ["*"];
|
|
68799
|
+
if (this.isRoot()) {
|
|
68800
|
+
const rulesyncFrontmatter = {
|
|
68801
|
+
targets,
|
|
68802
|
+
root: true,
|
|
68803
|
+
description: this.description,
|
|
68804
|
+
globs: ["**/*"]
|
|
68805
|
+
};
|
|
68806
|
+
return new RulesyncRule({
|
|
68807
|
+
outputRoot: this.getOutputRoot(),
|
|
68808
|
+
frontmatter: rulesyncFrontmatter,
|
|
68809
|
+
body: this.body,
|
|
68810
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
68811
|
+
relativeFilePath: RULESYNC_OVERVIEW_FILE_NAME,
|
|
68812
|
+
validate: true
|
|
68813
|
+
});
|
|
68814
|
+
}
|
|
68815
|
+
const sourceGlobs = normalizePatternList(this.frontmatter.globs) ?? [];
|
|
68816
|
+
const regex = normalizePatternList(this.frontmatter.regex);
|
|
68817
|
+
const isAlways = this.frontmatter.alwaysApply !== false;
|
|
68818
|
+
const globs = sourceGlobs.length === 0 && isAlways ? ["**/*"] : sourceGlobs;
|
|
68819
|
+
const { name, alwaysApply } = this.frontmatter;
|
|
68820
|
+
const hasContinueSection = name !== void 0 || alwaysApply !== void 0 || regex !== void 0;
|
|
68821
|
+
const rulesyncFrontmatter = {
|
|
68822
|
+
targets,
|
|
68823
|
+
root: false,
|
|
68824
|
+
description: this.frontmatter.description,
|
|
68825
|
+
globs,
|
|
68826
|
+
...hasContinueSection && { continue: {
|
|
68827
|
+
...name !== void 0 && { name },
|
|
68828
|
+
alwaysApply,
|
|
68829
|
+
regex
|
|
68830
|
+
} }
|
|
68831
|
+
};
|
|
68832
|
+
return new RulesyncRule({
|
|
68833
|
+
outputRoot: this.getOutputRoot(),
|
|
68834
|
+
frontmatter: rulesyncFrontmatter,
|
|
68835
|
+
body: this.body,
|
|
68836
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
68837
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
68838
|
+
validate: true
|
|
68839
|
+
});
|
|
68840
|
+
}
|
|
68841
|
+
validate() {
|
|
68842
|
+
if (!this.frontmatter) return {
|
|
68843
|
+
success: true,
|
|
68844
|
+
error: null
|
|
68845
|
+
};
|
|
68846
|
+
const result = ContinueRuleFrontmatterSchema.safeParse(this.frontmatter);
|
|
68847
|
+
if (result.success) return {
|
|
68848
|
+
success: true,
|
|
68849
|
+
error: null
|
|
68850
|
+
};
|
|
68851
|
+
return {
|
|
68852
|
+
success: false,
|
|
68853
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
68854
|
+
};
|
|
68855
|
+
}
|
|
68856
|
+
getFrontmatter() {
|
|
68857
|
+
return this.frontmatter;
|
|
68858
|
+
}
|
|
68859
|
+
getBody() {
|
|
68860
|
+
return this.body;
|
|
68861
|
+
}
|
|
68862
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
68863
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
68864
|
+
rulesyncRule,
|
|
68865
|
+
toolTarget: "continue"
|
|
68866
|
+
});
|
|
68867
|
+
}
|
|
68868
|
+
};
|
|
68869
|
+
//#endregion
|
|
67484
68870
|
//#region src/features/rules/copilot-rule.ts
|
|
67485
68871
|
const CopilotRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
67486
68872
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -71562,6 +72948,14 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
71562
72948
|
collisionPolicy: "fold"
|
|
71563
72949
|
}
|
|
71564
72950
|
}],
|
|
72951
|
+
["continue", {
|
|
72952
|
+
class: ContinueRule,
|
|
72953
|
+
meta: {
|
|
72954
|
+
extension: "md",
|
|
72955
|
+
supportsGlobal: true,
|
|
72956
|
+
ruleDiscoveryMode: "auto"
|
|
72957
|
+
}
|
|
72958
|
+
}],
|
|
71565
72959
|
["copilot", {
|
|
71566
72960
|
class: CopilotRule,
|
|
71567
72961
|
meta: {
|