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.
@@ -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$332 } = node_path.posix;
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$332(RULES_FEATURE_SUBDIR, ".curated");
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$332(SKILLS_FEATURE_SUBDIR, ".curated");
460
- const RULESYNC_RULES_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
461
- const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
462
- const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
463
- const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
464
- const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
465
- const RULESYNC_MCP_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
466
- const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
467
- const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
468
- join$332(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
469
- const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
470
- const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
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$332(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
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$332(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
476
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
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
- /** The `command` array a `local` server is spawned with, `args` merged in. */
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 commandArray = [];
30246
- if (serverConfig.command) {
30247
- if (Array.isArray(serverConfig.command)) commandArray.push(...serverConfig.command);
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 "${serverName}" because it declares ${reason}.`);
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 unchanged,
30350
- * as Bob documents all of them.
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$6(cliName) {
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$6(toolName);
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$5(augmentName) {
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$5(entry.toolName);
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$4(claudeName) {
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$4(toolName);
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: {