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.
@@ -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",
@@ -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$6(cliName) {
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$6(toolName);
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$5(augmentName) {
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$5(entry.toolName);
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$4(claudeName) {
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$4(toolName);
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: {