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.
@@ -478,6 +478,7 @@ const rulesProcessorToolTargetTuple = [
478
478
  "cline",
479
479
  "codebuddy",
480
480
  "codexcli",
481
+ "continue",
481
482
  "copilot",
482
483
  "copilotcli",
483
484
  "cortexcode",
@@ -521,6 +522,7 @@ const ignoreProcessorToolTargetTuple = [
521
522
  "claudecode",
522
523
  "claudecode-legacy",
523
524
  "cline",
525
+ "continue",
524
526
  "crush",
525
527
  "cursor",
526
528
  "hermesagent",
@@ -552,6 +554,7 @@ const mcpProcessorToolTargetTuple = [
552
554
  "claudecode-legacy",
553
555
  "cline",
554
556
  "codexcli",
557
+ "continue",
555
558
  "copilot",
556
559
  "copilotcli",
557
560
  "cortexcode",
@@ -593,6 +596,7 @@ const commandsProcessorToolTargetTuple = [
593
596
  "claudecode-legacy",
594
597
  "cline",
595
598
  "codexcli",
599
+ "continue",
596
600
  "copilot",
597
601
  "cursor",
598
602
  "factorydroid",
@@ -670,6 +674,7 @@ const skillsProcessorToolTargetTuple = [
670
674
  "claudecode-legacy",
671
675
  "cline",
672
676
  "codexcli",
677
+ "continue",
673
678
  "copilot",
674
679
  "copilotcli",
675
680
  "cortexcode",
@@ -715,6 +720,7 @@ const hooksProcessorToolTargetTuple = [
715
720
  "claudecode",
716
721
  "claudecode-plugin",
717
722
  "codexcli",
723
+ "continue",
718
724
  "copilot",
719
725
  "copilotcli",
720
726
  "cortexcode",
@@ -747,6 +753,7 @@ const permissionsProcessorToolTargetTuple = [
747
753
  "claudecode",
748
754
  "cline",
749
755
  "codexcli",
756
+ "continue",
750
757
  "copilot",
751
758
  "copilotcli",
752
759
  "cursor",
@@ -3259,6 +3266,7 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
3259
3266
  ".claude/settings.json",
3260
3267
  ".claude/settings.local.json",
3261
3268
  ".codex/config.toml",
3269
+ ".continue/settings.json",
3262
3270
  ".copilot/settings.json",
3263
3271
  ".github/copilot/settings.json",
3264
3272
  ".cortex/settings.json",
@@ -4883,6 +4891,59 @@ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
4883
4891
  };
4884
4892
  const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4885
4893
  /**
4894
+ * Hook events supported by the Continue CLI (`cn`).
4895
+ *
4896
+ * Continue reads a Claude-Code-compatible `hooks` key from
4897
+ * `~/.continue/settings.json` (user) and `.continue/settings.json` (project):
4898
+ * PascalCase event names, a regex `matcher` over the event's subject (tool
4899
+ * name, session source, ...), and `command` / `http` / `prompt` / `agent`
4900
+ * handlers with a `timeout` in seconds. `$CONTINUE_PROJECT_DIR` resolves to
4901
+ * the working directory inside commands. The event list is the CLI's
4902
+ * `HOOK_EVENT_NAMES`.
4903
+ *
4904
+ * @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/types.ts
4905
+ * @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/hookConfig.ts
4906
+ */
4907
+ const CONTINUE_HOOK_EVENTS = [
4908
+ "preToolUse",
4909
+ "postToolUse",
4910
+ "postToolUseFailure",
4911
+ "permissionRequest",
4912
+ "beforeSubmitPrompt",
4913
+ "sessionStart",
4914
+ "sessionEnd",
4915
+ "stop",
4916
+ "notification",
4917
+ "subagentStart",
4918
+ "subagentStop",
4919
+ "preCompact",
4920
+ "configChange",
4921
+ "teammateIdle",
4922
+ "taskCompleted",
4923
+ "worktreeCreate",
4924
+ "worktreeRemove"
4925
+ ];
4926
+ const CANONICAL_TO_CONTINUE_EVENT_NAMES = {
4927
+ preToolUse: "PreToolUse",
4928
+ postToolUse: "PostToolUse",
4929
+ postToolUseFailure: "PostToolUseFailure",
4930
+ permissionRequest: "PermissionRequest",
4931
+ beforeSubmitPrompt: "UserPromptSubmit",
4932
+ sessionStart: "SessionStart",
4933
+ sessionEnd: "SessionEnd",
4934
+ stop: "Stop",
4935
+ notification: "Notification",
4936
+ subagentStart: "SubagentStart",
4937
+ subagentStop: "SubagentStop",
4938
+ preCompact: "PreCompact",
4939
+ configChange: "ConfigChange",
4940
+ teammateIdle: "TeammateIdle",
4941
+ taskCompleted: "TaskCompleted",
4942
+ worktreeCreate: "WorktreeCreate",
4943
+ worktreeRemove: "WorktreeRemove"
4944
+ };
4945
+ const CONTINUE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CONTINUE_EVENT_NAMES).map(([k, v]) => [v, k]));
4946
+ /**
4886
4947
  * Hook events supported by Hermes Agent's native Shell Hooks system.
4887
4948
  *
4888
4949
  * Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
@@ -5037,6 +5098,7 @@ const HooksConfigSchema = z.looseObject({
5037
5098
  zcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5038
5099
  bob: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5039
5100
  cortexcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5101
+ continue: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5040
5102
  tabnine: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5041
5103
  qwencode: z.optional(z.looseObject({
5042
5104
  hooks: z.optional(hooksRecordSchema),
@@ -6043,6 +6105,7 @@ const RulesyncMcpFileSchema = z.looseObject({
6043
6105
  claudecode: z.optional(toolScopedMcpSchema),
6044
6106
  cline: z.optional(toolScopedMcpSchema),
6045
6107
  codexcli: z.optional(toolScopedMcpSchema),
6108
+ continue: z.optional(toolScopedMcpSchema),
6046
6109
  copilot: z.optional(toolScopedMcpSchema),
6047
6110
  copilotcli: z.optional(toolScopedMcpSchema),
6048
6111
  cursor: z.optional(toolScopedMcpSchema),
@@ -7676,6 +7739,7 @@ const PermissionsConfigSchema = z.looseObject({
7676
7739
  zed: z.optional(ZedPermissionsOverrideSchema),
7677
7740
  devin: z.optional(DevinPermissionsOverrideSchema),
7678
7741
  "antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
7742
+ continue: z.optional(CanonicalPermissionsOverrideSchema),
7679
7743
  copilot: z.optional(CanonicalPermissionsOverrideSchema),
7680
7744
  copilotcli: z.optional(CanonicalPermissionsOverrideSchema),
7681
7745
  goose: z.optional(CanonicalPermissionsOverrideSchema),
@@ -8104,6 +8168,13 @@ const RulesyncRuleFrontmatterSchema = z.object({
8104
8168
  enabled: z.optional(z.boolean()),
8105
8169
  description: z.optional(z.string())
8106
8170
  })),
8171
+ continue: z.optional(z.looseObject({
8172
+ alwaysApply: z.optional(z.boolean()),
8173
+ description: z.optional(z.string()),
8174
+ globs: z.optional(z.union([z.string(), z.array(z.string())])),
8175
+ name: z.optional(z.string()),
8176
+ regex: z.optional(z.union([z.string(), z.array(z.string())]))
8177
+ })),
8107
8178
  cursor: z.optional(z.looseObject({
8108
8179
  alwaysApply: z.optional(z.boolean()),
8109
8180
  description: z.optional(z.string()),
@@ -9392,6 +9463,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
9392
9463
  "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
9393
9464
  })),
9394
9465
  cortexcode: z.optional(z.looseObject({})),
9466
+ continue: z.optional(z.looseObject({})),
9395
9467
  tabnine: z.optional(z.looseObject({})),
9396
9468
  takt: z.optional(z.looseObject({
9397
9469
  name: z.optional(z.string()),
@@ -12971,6 +13043,14 @@ const SHARED_CONFIG_OWNERSHIP = {
12971
13043
  ownedKeys: ["hooks"]
12972
13044
  } }
12973
13045
  },
13046
+ ".continue/settings.json": {
13047
+ format: "json",
13048
+ invalidRootPolicy: "error",
13049
+ features: { hooks: {
13050
+ kind: "replace-owned-keys",
13051
+ ownedKeys: ["hooks"]
13052
+ } }
13053
+ },
12974
13054
  ".cortex/settings.json": {
12975
13055
  format: "json",
12976
13056
  invalidRootPolicy: "error",
@@ -15172,6 +15252,152 @@ var CodexcliCommand = class CodexcliCommand extends ToolCommand {
15172
15252
  }
15173
15253
  };
15174
15254
  //#endregion
15255
+ //#region src/constants/continue-paths.ts
15256
+ const CONTINUE_DIR = ".continue";
15257
+ const CONTINUE_ROOT_RULE_FILE_NAME = "AGENTS.md";
15258
+ const CONTINUE_PROMPTS_DIR_PATH = join(CONTINUE_DIR, "prompts");
15259
+ const CONTINUE_MCP_DIR_PATH = join(CONTINUE_DIR, "mcpServers");
15260
+ const CONTINUE_MCP_FILE_NAME = "mcp.json";
15261
+ const CONTINUE_SKILLS_DIR_PATH = join(CONTINUE_DIR, "skills");
15262
+ const CONTINUE_SETTINGS_FILE_NAME = "settings.json";
15263
+ const CONTINUE_PERMISSIONS_FILE_NAME = "permissions.yaml";
15264
+ const CONTINUE_IGNORE_FILE_NAME = ".continueignore";
15265
+ //#endregion
15266
+ //#region src/features/commands/continue-command.ts
15267
+ /**
15268
+ * Continue prompt files are Markdown files under `<project>/.continue/prompts/`
15269
+ * (project scope) and `~/.continue/prompts/` (user scope). A prompt with
15270
+ * `invokable: true` becomes a `/name` slash command in the chat input and the
15271
+ * CLI; without it the file is treated as a plain rule, so rulesync always sets
15272
+ * the flag. `name` is the command name (the file stem by default) and
15273
+ * `description` the hint shown next to it.
15274
+ *
15275
+ * @see https://docs.continue.dev/customize/deep-dives/prompts
15276
+ */
15277
+ const ContinueCommandFrontmatterSchema = z.looseObject({
15278
+ name: z.optional(z.string()),
15279
+ description: z.optional(z.string()),
15280
+ invokable: z.optional(z.boolean())
15281
+ });
15282
+ /** The command name Continue derives from a prompt file: its stem. */
15283
+ function commandNameFromFilePath(relativeFilePath) {
15284
+ return basename(relativeFilePath).replace(/\.md$/, "");
15285
+ }
15286
+ var ContinueCommand = class ContinueCommand extends ToolCommand {
15287
+ frontmatter;
15288
+ body;
15289
+ constructor({ frontmatter, body, ...rest }) {
15290
+ if (rest.validate) {
15291
+ const result = ContinueCommandFrontmatterSchema.safeParse(frontmatter);
15292
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
15293
+ }
15294
+ super({
15295
+ ...rest,
15296
+ fileContent: stringifyFrontmatter(body, frontmatter)
15297
+ });
15298
+ this.frontmatter = frontmatter;
15299
+ this.body = body;
15300
+ }
15301
+ static getSettablePaths(_options = {}) {
15302
+ return { relativeDirPath: CONTINUE_PROMPTS_DIR_PATH };
15303
+ }
15304
+ getBody() {
15305
+ return this.body;
15306
+ }
15307
+ getFrontmatter() {
15308
+ return this.frontmatter;
15309
+ }
15310
+ toRulesyncCommand() {
15311
+ const { description, name, invokable: _invokable, ...restFields } = this.frontmatter;
15312
+ const continueFields = {
15313
+ ...name !== void 0 && name !== commandNameFromFilePath(this.relativeFilePath) && { name },
15314
+ ...restFields
15315
+ };
15316
+ const rulesyncFrontmatter = {
15317
+ targets: ["*"],
15318
+ description,
15319
+ ...Object.keys(continueFields).length > 0 && { continue: continueFields }
15320
+ };
15321
+ const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
15322
+ return new RulesyncCommand({
15323
+ outputRoot: ".",
15324
+ frontmatter: rulesyncFrontmatter,
15325
+ body: this.body,
15326
+ relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
15327
+ relativeFilePath: this.relativeFilePath,
15328
+ fileContent,
15329
+ validate: true
15330
+ });
15331
+ }
15332
+ static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
15333
+ const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
15334
+ const relativeFilePath = rulesyncCommand.getRelativeFilePath();
15335
+ const continueFields = rulesyncFrontmatter.continue ?? {};
15336
+ const continueFrontmatter = {
15337
+ name: commandNameFromFilePath(relativeFilePath),
15338
+ description: rulesyncFrontmatter.description,
15339
+ ...continueFields,
15340
+ invokable: true
15341
+ };
15342
+ const body = rulesyncCommand.getBody();
15343
+ const paths = this.getSettablePaths({ global });
15344
+ return new ContinueCommand({
15345
+ outputRoot,
15346
+ frontmatter: continueFrontmatter,
15347
+ body,
15348
+ relativeDirPath: paths.relativeDirPath,
15349
+ relativeFilePath,
15350
+ validate
15351
+ });
15352
+ }
15353
+ validate() {
15354
+ if (!this.frontmatter) return {
15355
+ success: true,
15356
+ error: null
15357
+ };
15358
+ const result = ContinueCommandFrontmatterSchema.safeParse(this.frontmatter);
15359
+ if (result.success) return {
15360
+ success: true,
15361
+ error: null
15362
+ };
15363
+ return {
15364
+ success: false,
15365
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
15366
+ };
15367
+ }
15368
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
15369
+ return this.isTargetedByRulesyncCommandDefault({
15370
+ rulesyncCommand,
15371
+ toolTarget: "continue"
15372
+ });
15373
+ }
15374
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
15375
+ const paths = this.getSettablePaths({ global });
15376
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
15377
+ const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
15378
+ const result = ContinueCommandFrontmatterSchema.safeParse(frontmatter);
15379
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
15380
+ return new ContinueCommand({
15381
+ outputRoot,
15382
+ relativeDirPath: paths.relativeDirPath,
15383
+ relativeFilePath,
15384
+ frontmatter: result.data,
15385
+ body: content.trim(),
15386
+ validate
15387
+ });
15388
+ }
15389
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
15390
+ return new ContinueCommand({
15391
+ outputRoot,
15392
+ relativeDirPath,
15393
+ relativeFilePath,
15394
+ frontmatter: { description: "" },
15395
+ body: "",
15396
+ validate: false
15397
+ });
15398
+ }
15399
+ };
15400
+ //#endregion
15175
15401
  //#region src/constants/copilot-paths.ts
15176
15402
  const COPILOT_DIR = ".copilot";
15177
15403
  const GITHUB_DIR = ".github";
@@ -19272,6 +19498,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
19272
19498
  supportsSubdirectory: false
19273
19499
  }
19274
19500
  }],
19501
+ ["continue", {
19502
+ class: ContinueCommand,
19503
+ meta: {
19504
+ extension: "md",
19505
+ supportsProject: true,
19506
+ supportsGlobal: true,
19507
+ isSimulated: false,
19508
+ supportsSubdirectory: false
19509
+ }
19510
+ }],
19275
19511
  ["copilot", {
19276
19512
  class: CopilotCommand,
19277
19513
  meta: {
@@ -22621,6 +22857,159 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
22621
22857
  }
22622
22858
  };
22623
22859
  //#endregion
22860
+ //#region src/features/hooks/continue-hooks.ts
22861
+ const CONTINUE_CONVERTER_CONFIG = {
22862
+ supportedEvents: CONTINUE_HOOK_EVENTS,
22863
+ canonicalToToolEventNames: CANONICAL_TO_CONTINUE_EVENT_NAMES,
22864
+ toolToCanonicalEventNames: CONTINUE_TO_CANONICAL_EVENT_NAMES,
22865
+ projectDirVar: "$CONTINUE_PROJECT_DIR",
22866
+ prefixDotRelativeCommandsOnly: true,
22867
+ noMatcherEvents: /* @__PURE__ */ new Set([
22868
+ "beforeSubmitPrompt",
22869
+ "stop",
22870
+ "teammateIdle",
22871
+ "taskCompleted",
22872
+ "worktreeCreate",
22873
+ "worktreeRemove"
22874
+ ]),
22875
+ supportedHookTypes: /* @__PURE__ */ new Set([
22876
+ "command",
22877
+ "prompt",
22878
+ "http",
22879
+ "agent"
22880
+ ]),
22881
+ emitsPromptModel: true,
22882
+ stringPassthroughFields: [{
22883
+ canonical: "statusMessage",
22884
+ tool: "statusMessage"
22885
+ }],
22886
+ booleanPassthroughFields: [{
22887
+ canonical: "once",
22888
+ tool: "once"
22889
+ }, {
22890
+ canonical: "async",
22891
+ tool: "async",
22892
+ commandOnly: true
22893
+ }]
22894
+ };
22895
+ /**
22896
+ * Single spelling of the settings/hooks codec/policy: fail closed on an
22897
+ * unparseable root rather than replacing the user's Continue settings with
22898
+ * generated output.
22899
+ */
22900
+ function parseContinueSettings(fileContent, filePath) {
22901
+ return parseSharedConfig({
22902
+ format: "json",
22903
+ fileContent,
22904
+ filePath,
22905
+ invalidRootPolicy: "error"
22906
+ });
22907
+ }
22908
+ /**
22909
+ * Continue CLI hooks.
22910
+ *
22911
+ * Hooks live under the top-level `hooks` key of `<project>/.continue/settings.json`
22912
+ * (project scope) and `~/.continue/settings.json` (user scope), in the
22913
+ * Claude-Code shape: `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{
22914
+ * "type": "command" | "http" | "prompt" | "agent", ..., "timeout"?: <seconds>
22915
+ * }] }] }`. Both files also hold settings rulesync does not own, so generation
22916
+ * merges the `hooks` key into the existing file (see `SHARED_CONFIG_OWNERSHIP`)
22917
+ * instead of overwriting it. The CLI additionally reads
22918
+ * `.continue/settings.local.json` and the Claude Code settings files; rulesync
22919
+ * writes only the committable project file and the user file.
22920
+ *
22921
+ * @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/hookConfig.ts
22922
+ * @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/types.ts
22923
+ */
22924
+ var ContinueHooks = class ContinueHooks extends ToolHooks {
22925
+ constructor(params) {
22926
+ super({
22927
+ ...params,
22928
+ fileContent: params.fileContent ?? "{}"
22929
+ });
22930
+ }
22931
+ isDeletable() {
22932
+ return false;
22933
+ }
22934
+ static getSettablePaths(_options = {}) {
22935
+ return {
22936
+ relativeDirPath: CONTINUE_DIR,
22937
+ relativeFilePath: CONTINUE_SETTINGS_FILE_NAME
22938
+ };
22939
+ }
22940
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
22941
+ const paths = ContinueHooks.getSettablePaths({ global });
22942
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
22943
+ return new ContinueHooks({
22944
+ outputRoot,
22945
+ relativeDirPath: paths.relativeDirPath,
22946
+ relativeFilePath: paths.relativeFilePath,
22947
+ fileContent,
22948
+ validate
22949
+ });
22950
+ }
22951
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
22952
+ const paths = ContinueHooks.getSettablePaths({ global });
22953
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
22954
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
22955
+ const config = rulesyncHooks.getJson();
22956
+ const hooks = canonicalToToolHooks({
22957
+ config,
22958
+ toolOverrideHooks: config.continue?.hooks,
22959
+ converterConfig: CONTINUE_CONVERTER_CONFIG,
22960
+ logger
22961
+ });
22962
+ const fileContent = applySharedConfigPatch({
22963
+ fileKey: sharedConfigFileKey(paths),
22964
+ feature: "hooks",
22965
+ existingContent,
22966
+ patch: { hooks },
22967
+ filePath,
22968
+ logger
22969
+ });
22970
+ return new ContinueHooks({
22971
+ outputRoot,
22972
+ relativeDirPath: paths.relativeDirPath,
22973
+ relativeFilePath: paths.relativeFilePath,
22974
+ fileContent,
22975
+ validate
22976
+ });
22977
+ }
22978
+ toRulesyncHooks({ logger } = {}) {
22979
+ const configPath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
22980
+ let settings;
22981
+ try {
22982
+ settings = parseContinueSettings(this.getFileContent(), configPath);
22983
+ } catch (error) {
22984
+ throw new Error(`Failed to parse Continue hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
22985
+ }
22986
+ const hooks = toolHooksToCanonical({
22987
+ logger,
22988
+ hooks: settings.hooks,
22989
+ converterConfig: CONTINUE_CONVERTER_CONFIG
22990
+ });
22991
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
22992
+ hooks,
22993
+ overrideKey: "continue"
22994
+ }), null, 2) });
22995
+ }
22996
+ validate() {
22997
+ return {
22998
+ success: true,
22999
+ error: null
23000
+ };
23001
+ }
23002
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
23003
+ return new ContinueHooks({
23004
+ outputRoot,
23005
+ relativeDirPath,
23006
+ relativeFilePath,
23007
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
23008
+ validate: false
23009
+ });
23010
+ }
23011
+ };
23012
+ //#endregion
22624
23013
  //#region src/features/hooks/copilot-hooks.ts
22625
23014
  /**
22626
23015
  * Copilot hook entry as stored in .github/hooks/copilot-hooks.json.
@@ -27603,6 +27992,35 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
27603
27992
  supportsMatcher: true,
27604
27993
  matcherEvents: ["preToolUse", "postToolUse"]
27605
27994
  }],
27995
+ ["continue", {
27996
+ class: ContinueHooks,
27997
+ meta: {
27998
+ supportsProject: true,
27999
+ supportsGlobal: true,
28000
+ supportsImport: true
28001
+ },
28002
+ supportedEvents: CONTINUE_HOOK_EVENTS,
28003
+ supportedHookTypes: [
28004
+ "command",
28005
+ "http",
28006
+ "prompt",
28007
+ "agent"
28008
+ ],
28009
+ supportsMatcher: true,
28010
+ matcherEvents: [
28011
+ "preToolUse",
28012
+ "postToolUse",
28013
+ "postToolUseFailure",
28014
+ "permissionRequest",
28015
+ "sessionStart",
28016
+ "sessionEnd",
28017
+ "notification",
28018
+ "subagentStart",
28019
+ "subagentStop",
28020
+ "preCompact",
28021
+ "configChange"
28022
+ ]
28023
+ }],
27606
28024
  ["cortexcode", {
27607
28025
  class: CortexcodeHooks,
27608
28026
  meta: {
@@ -28323,6 +28741,60 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
28323
28741
  }
28324
28742
  };
28325
28743
  //#endregion
28744
+ //#region src/features/ignore/continue-ignore.ts
28745
+ /**
28746
+ * Continue ignore file implementation.
28747
+ *
28748
+ * - Project scope: the workspace-root `.continueignore`, which follows the
28749
+ * exact same rules as `.gitignore` and is layered on top of it.
28750
+ * - Global scope: `~/.continue/.continueignore`, respected for all workspaces.
28751
+ *
28752
+ * @see https://docs.continue.dev/reference/deprecated-codebase
28753
+ */
28754
+ var ContinueIgnore = class ContinueIgnore extends ToolIgnore {
28755
+ static getSettablePaths({ global = false } = {}) {
28756
+ return {
28757
+ relativeDirPath: global ? CONTINUE_DIR : ".",
28758
+ relativeFilePath: CONTINUE_IGNORE_FILE_NAME
28759
+ };
28760
+ }
28761
+ toRulesyncIgnore() {
28762
+ return this.toRulesyncIgnoreDefault();
28763
+ }
28764
+ static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
28765
+ const paths = this.getSettablePaths({ global });
28766
+ return new ContinueIgnore({
28767
+ outputRoot,
28768
+ relativeDirPath: paths.relativeDirPath,
28769
+ relativeFilePath: paths.relativeFilePath,
28770
+ fileContent: rulesyncIgnore.getFileContent(),
28771
+ global
28772
+ });
28773
+ }
28774
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
28775
+ const { relativeDirPath, relativeFilePath } = this.getSettablePaths({ global });
28776
+ const fileContent = await readFileContent(join(outputRoot, relativeDirPath, relativeFilePath));
28777
+ return new ContinueIgnore({
28778
+ outputRoot,
28779
+ relativeDirPath,
28780
+ relativeFilePath,
28781
+ fileContent,
28782
+ validate,
28783
+ global
28784
+ });
28785
+ }
28786
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
28787
+ return new ContinueIgnore({
28788
+ outputRoot,
28789
+ relativeDirPath,
28790
+ relativeFilePath,
28791
+ fileContent: "",
28792
+ validate: false,
28793
+ global
28794
+ });
28795
+ }
28796
+ };
28797
+ //#endregion
28326
28798
  //#region src/constants/crush-paths.ts
28327
28799
  const CRUSH_RULE_FILE_NAME = "CRUSH.md";
28328
28800
  const CRUSH_GLOBAL_DIR = join(".config", "crush");
@@ -29437,6 +29909,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
29437
29909
  ["claudecode", { class: ClaudecodeIgnore }],
29438
29910
  ["claudecode-legacy", { class: ClaudecodeIgnore }],
29439
29911
  ["cline", { class: ClineIgnore }],
29912
+ ["continue", { class: ContinueIgnore }],
29440
29913
  ["crush", { class: CrushIgnore }],
29441
29914
  ["cursor", { class: CursorIgnore }],
29442
29915
  ["hermesagent", { class: HermesagentIgnore }],
@@ -29457,6 +29930,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
29457
29930
  ]);
29458
29931
  const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
29459
29932
  const ignoreProcessorGlobalToolTargets = [
29933
+ "continue",
29460
29934
  "devin",
29461
29935
  "kiro",
29462
29936
  "kiro-cli",
@@ -31160,6 +31634,230 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
31160
31634
  }
31161
31635
  };
31162
31636
  //#endregion
31637
+ //#region src/features/mcp/continue-mcp.ts
31638
+ /**
31639
+ * Parse a Continue MCP file. Continue reads the files under `mcpServers/` as
31640
+ * JSONC, so comments and trailing commas in a hand-written file are accepted;
31641
+ * malformed content or a non-object root (`null`, an array, a scalar) fails
31642
+ * closed rather than being spread into the regenerated file.
31643
+ */
31644
+ function parseContinueMcpConfig({ fileContent, relativePath }) {
31645
+ let parsed;
31646
+ try {
31647
+ parsed = parseJsonc(fileContent);
31648
+ } catch (error) {
31649
+ throw new Error(`Failed to parse Continue MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
31650
+ }
31651
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Continue MCP config at ${relativePath}: expected a JSON object at the root`);
31652
+ return parsed;
31653
+ }
31654
+ /**
31655
+ * The remote transport Continue reads a server as. Continue's JSON schema
31656
+ * accepts `type: "sse"` or `type: "http"` (streamable HTTP) for a `url`
31657
+ * server; a bare `url` defaults to `http`, and the canonical
31658
+ * `streamable-http` spelling is folded into it. A `ws(s)://` URL or any other
31659
+ * stated transport has no Continue equivalent, so `undefined` tells the
31660
+ * caller to skip the server.
31661
+ * @see https://docs.continue.dev/customize/deep-dives/mcp
31662
+ */
31663
+ function asContinueRemoteType(stated, url) {
31664
+ if (stated === "sse") return "sse";
31665
+ if (stated === "http" || stated === "streamable-http") return "http";
31666
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
31667
+ }
31668
+ /**
31669
+ * Convert the canonical server map to the shape Continue's JSON MCP schema
31670
+ * accepts: a remote server is `{ type, url, headers? }` and a stdio server is
31671
+ * `{ type: "stdio", command, args?, env? }`. Continue validates the whole
31672
+ * file with a strict union — one server that matches neither shape (an
31673
+ * unknown transport, a non-string `env` value) makes Continue drop every
31674
+ * server in the file — so only the documented keys are emitted and anything
31675
+ * else (`timeout`, `oauth`, rulesync-only fields) is left out. `envFile` is
31676
+ * accepted by Continue's schema but ignored with a warning at load time, so
31677
+ * it is dropped here with a warning instead of being written as if it worked.
31678
+ *
31679
+ * A server Continue cannot start or reach — no transport at all, a remote
31680
+ * transport without a URL, a WebSocket URL, or a stdio entry without a
31681
+ * command — is skipped with a warning rather than written in a form that
31682
+ * would invalidate the file.
31683
+ * @see https://docs.continue.dev/customize/deep-dives/mcp
31684
+ */
31685
+ function convertToContinueFormat(mcpServers, logger) {
31686
+ const result = {};
31687
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
31688
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
31689
+ if (declaresNoTransport(serverConfig)) {
31690
+ warnAndSkipMcpServer({
31691
+ toolName: "Continue",
31692
+ serverName,
31693
+ reason: "no transport",
31694
+ logger
31695
+ });
31696
+ continue;
31697
+ }
31698
+ const converted = isRemoteMcpServer(serverConfig) ? convertRemoteServer({
31699
+ serverName,
31700
+ serverConfig,
31701
+ logger
31702
+ }) : convertStdioServer({
31703
+ serverName,
31704
+ serverConfig,
31705
+ logger
31706
+ });
31707
+ if (converted !== void 0) result[serverName] = converted;
31708
+ }
31709
+ return result;
31710
+ }
31711
+ function convertRemoteServer({ serverName, serverConfig, logger }) {
31712
+ const url = resolveRemoteMcpUrl(serverConfig);
31713
+ if (!url) {
31714
+ warnAndSkipMcpServer({
31715
+ toolName: "Continue",
31716
+ serverName,
31717
+ reason: "a remote transport without a url",
31718
+ logger
31719
+ });
31720
+ return;
31721
+ }
31722
+ const stated = serverConfig.type ?? serverConfig.transport;
31723
+ const remoteType = asContinueRemoteType(typeof stated === "string" ? stated : void 0, url);
31724
+ if (remoteType === void 0) {
31725
+ warnAndSkipMcpServer({
31726
+ toolName: "Continue",
31727
+ serverName,
31728
+ 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)`,
31729
+ logger
31730
+ });
31731
+ return;
31732
+ }
31733
+ const converted = {
31734
+ type: remoteType,
31735
+ url
31736
+ };
31737
+ if (isRecord$1(serverConfig.headers)) converted.headers = omitPrototypePollutionKeys(serverConfig.headers);
31738
+ return converted;
31739
+ }
31740
+ function convertStdioServer({ serverName, serverConfig, logger }) {
31741
+ const [command, ...args] = (typeof serverConfig.command === "string" ? serverConfig.command !== "" : Array.isArray(serverConfig.command) && serverConfig.command.length > 0) ? resolveLocalMcpCommand(serverConfig) : [];
31742
+ if (!command) {
31743
+ warnAndSkipMcpServer({
31744
+ toolName: "Continue",
31745
+ serverName,
31746
+ reason: "a stdio transport without a command",
31747
+ logger
31748
+ });
31749
+ return;
31750
+ }
31751
+ const converted = {
31752
+ type: "stdio",
31753
+ command
31754
+ };
31755
+ if (args.length > 0) converted.args = args;
31756
+ if (isRecord$1(serverConfig.env)) converted.env = omitPrototypePollutionKeys(serverConfig.env);
31757
+ 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.`);
31758
+ return converted;
31759
+ }
31760
+ /**
31761
+ * Convert Continue's server map back to the canonical shape. The documented
31762
+ * spelling (`type` + `command`/`url`) is already canonical, so entries pass
31763
+ * through with only prototype-pollution keys dropped.
31764
+ */
31765
+ function convertFromContinueFormat(mcpServers) {
31766
+ if (!isMcpServers(mcpServers)) return {};
31767
+ const result = {};
31768
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
31769
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
31770
+ result[serverName] = omitPrototypePollutionKeys(serverConfig);
31771
+ }
31772
+ return result;
31773
+ }
31774
+ /**
31775
+ * Continue MCP configuration.
31776
+ *
31777
+ * Continue loads every `*.json` file under `.continue/mcpServers/` (project)
31778
+ * and `~/.continue/mcpServers/` (global); rulesync owns one of them,
31779
+ * `mcp.json`, in the `{ "mcpServers": { ... } }` shape the docs show. Other
31780
+ * files in the directory, and top-level sibling keys of `mcp.json`, are left
31781
+ * alone. The global file is not deleted by `--delete` (it lives outside the
31782
+ * project), while the project file is rulesync's own and is.
31783
+ *
31784
+ * @see https://docs.continue.dev/customize/deep-dives/mcp
31785
+ */
31786
+ var ContinueMcp = class ContinueMcp extends ToolMcp {
31787
+ json;
31788
+ constructor(params) {
31789
+ super(params);
31790
+ this.json = this.fileContent === void 0 ? {} : parseContinueMcpConfig({
31791
+ fileContent: this.fileContent,
31792
+ relativePath: join(this.relativeDirPath, this.relativeFilePath)
31793
+ });
31794
+ }
31795
+ getJson() {
31796
+ return this.json;
31797
+ }
31798
+ isDeletable() {
31799
+ return !this.global;
31800
+ }
31801
+ static getSettablePaths(_options = {}) {
31802
+ return {
31803
+ relativeDirPath: CONTINUE_MCP_DIR_PATH,
31804
+ relativeFilePath: CONTINUE_MCP_FILE_NAME
31805
+ };
31806
+ }
31807
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
31808
+ const paths = this.getSettablePaths({ global });
31809
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
31810
+ return new ContinueMcp({
31811
+ outputRoot,
31812
+ relativeDirPath: paths.relativeDirPath,
31813
+ relativeFilePath: paths.relativeFilePath,
31814
+ fileContent,
31815
+ validate,
31816
+ global
31817
+ });
31818
+ }
31819
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
31820
+ const paths = this.getSettablePaths({ global });
31821
+ const json = parseContinueMcpConfig({
31822
+ fileContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
31823
+ relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
31824
+ });
31825
+ const mcpServers = convertToContinueFormat(rulesyncMcp.getMcpServers(), logger);
31826
+ const continueConfig = {
31827
+ ...json,
31828
+ mcpServers
31829
+ };
31830
+ return new ContinueMcp({
31831
+ outputRoot,
31832
+ relativeDirPath: paths.relativeDirPath,
31833
+ relativeFilePath: paths.relativeFilePath,
31834
+ fileContent: JSON.stringify(continueConfig, null, 2),
31835
+ validate,
31836
+ global
31837
+ });
31838
+ }
31839
+ toRulesyncMcp() {
31840
+ const mcpServers = convertFromContinueFormat(this.json.mcpServers);
31841
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
31842
+ }
31843
+ validate() {
31844
+ return {
31845
+ success: true,
31846
+ error: null
31847
+ };
31848
+ }
31849
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
31850
+ return new ContinueMcp({
31851
+ outputRoot,
31852
+ relativeDirPath,
31853
+ relativeFilePath,
31854
+ fileContent: "{}",
31855
+ validate: false,
31856
+ global
31857
+ });
31858
+ }
31859
+ };
31860
+ //#endregion
31163
31861
  //#region src/features/mcp/copilot-mcp.ts
31164
31862
  function convertFromCopilotFormat(copilotConfig) {
31165
31863
  return copilotConfig.servers ?? {};
@@ -36892,6 +37590,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
36892
37590
  supportsDisabledTools: true
36893
37591
  }
36894
37592
  }],
37593
+ ["continue", {
37594
+ class: ContinueMcp,
37595
+ meta: {
37596
+ supportsProject: true,
37597
+ supportsGlobal: true,
37598
+ supportsEnabledTools: false,
37599
+ supportsDisabledTools: false
37600
+ }
37601
+ }],
36895
37602
  ["copilot", {
36896
37603
  class: CopilotMcp,
36897
37604
  meta: {
@@ -38350,7 +39057,7 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
38350
39057
  function toAntigravityCliToolName(canonical) {
38351
39058
  return CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES[canonical] ?? canonical;
38352
39059
  }
38353
- function toCanonicalToolName$6(cliName) {
39060
+ function toCanonicalToolName$7(cliName) {
38354
39061
  return ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES[cliName] ?? cliName;
38355
39062
  }
38356
39063
  /**
@@ -38566,7 +39273,7 @@ function convertAntigravityCliToRulesyncPermissions(params) {
38566
39273
  const processEntries = (entries, action) => {
38567
39274
  for (const entry of entries) {
38568
39275
  const { toolName, pattern } = parsePermissionEntry$1(entry);
38569
- const canonical = toCanonicalToolName$6(toolName);
39276
+ const canonical = toCanonicalToolName$7(toolName);
38570
39277
  if (!permission[canonical]) permission[canonical] = {};
38571
39278
  permission[canonical][pattern] = action;
38572
39279
  }
@@ -38826,7 +39533,7 @@ const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONI
38826
39533
  function toAugmentToolName(canonical) {
38827
39534
  return CANONICAL_TO_AUGMENT_TOOL_NAMES[canonical] ?? canonical;
38828
39535
  }
38829
- function toCanonicalToolName$5(augmentName) {
39536
+ function toCanonicalToolName$6(augmentName) {
38830
39537
  return AUGMENT_TO_CANONICAL_TOOL_NAMES[augmentName] ?? augmentName;
38831
39538
  }
38832
39539
  function actionToAugmentType(action) {
@@ -39273,7 +39980,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
39273
39980
  if (isSpecialEntry(entry)) continue;
39274
39981
  const type = entry.permission.type;
39275
39982
  if (!isBasicAugmentType(type)) continue;
39276
- const canonical = toCanonicalToolName$5(entry.toolName);
39983
+ const canonical = toCanonicalToolName$6(entry.toolName);
39277
39984
  if (forbiddenMapKeys.has(canonical)) {
39278
39985
  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.`);
39279
39986
  continue;
@@ -39508,7 +40215,7 @@ const CLAUDE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONIC
39508
40215
  function toClaudeToolName(canonical) {
39509
40216
  return CANONICAL_TO_CLAUDE_TOOL_NAMES[canonical] ?? canonical;
39510
40217
  }
39511
- function toCanonicalToolName$4(claudeName) {
40218
+ function toCanonicalToolName$5(claudeName) {
39512
40219
  return CLAUDE_TO_CANONICAL_TOOL_NAMES[claudeName] ?? claudeName;
39513
40220
  }
39514
40221
  /**
@@ -40403,7 +41110,7 @@ function convertClaudeToRulesyncPermissions(params) {
40403
41110
  const processEntries = (entries, action) => {
40404
41111
  for (const entry of entries) {
40405
41112
  const { toolName, pattern } = parseClaudePermissionEntry(entry);
40406
- const canonical = toCanonicalToolName$4(toolName);
41113
+ const canonical = toCanonicalToolName$5(toolName);
40407
41114
  if (!permission[canonical]) permission[canonical] = {};
40408
41115
  permission[canonical][pattern] = action;
40409
41116
  }
@@ -41226,6 +41933,264 @@ function mapBashActionToDecision(action) {
41226
41933
  return "forbidden";
41227
41934
  }
41228
41935
  //#endregion
41936
+ //#region src/features/permissions/continue-permissions.ts
41937
+ const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
41938
+ const CATCH_ALL_PATTERN$4 = "*";
41939
+ const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
41940
+ bash: "Bash",
41941
+ read: "Read",
41942
+ edit: "Edit",
41943
+ write: "Write",
41944
+ webfetch: "Fetch"
41945
+ };
41946
+ const CONTINUE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CONTINUE_TOOL_NAMES).map(([k, v]) => [v, k]));
41947
+ const ACTION_TO_CONTINUE_LIST = {
41948
+ allow: "allow",
41949
+ ask: "ask",
41950
+ deny: "exclude"
41951
+ };
41952
+ const CONTINUE_LIST_TO_ACTION = {
41953
+ allow: "allow",
41954
+ ask: "ask",
41955
+ exclude: "deny"
41956
+ };
41957
+ const CONTINUE_PERMISSION_LIST_KEYS = [
41958
+ "exclude",
41959
+ "ask",
41960
+ "allow"
41961
+ ];
41962
+ const CONTINUE_PATTERN_RE = /^([^(]+)(?:\(([^)]*)\))?$/;
41963
+ function toContinueToolName(canonical) {
41964
+ return Object.hasOwn(CANONICAL_TO_CONTINUE_TOOL_NAMES, canonical) ? CANONICAL_TO_CONTINUE_TOOL_NAMES[canonical] ?? canonical : canonical;
41965
+ }
41966
+ function toCanonicalToolName$4(continueName) {
41967
+ return Object.hasOwn(CONTINUE_TO_CANONICAL_TOOL_NAMES, continueName) ? CONTINUE_TO_CANONICAL_TOOL_NAMES[continueName] ?? continueName : continueName;
41968
+ }
41969
+ /**
41970
+ * Build a permissions.yaml entry: the bare tool name for the catch-all, else
41971
+ * `Tool(pattern)`.
41972
+ */
41973
+ function buildContinuePermissionEntry(toolName, pattern) {
41974
+ return pattern === CATCH_ALL_PATTERN$4 ? toolName : `${toolName}(${pattern})`;
41975
+ }
41976
+ /**
41977
+ * Split a permissions.yaml entry into its tool name and argument pattern, or
41978
+ * `undefined` for an entry outside the loader's grammar.
41979
+ */
41980
+ function parseContinuePermissionEntry(entry) {
41981
+ const match = CONTINUE_PATTERN_RE.exec(entry);
41982
+ if (!match) return;
41983
+ const toolName = match[1]?.trim() ?? "";
41984
+ const pattern = match[2]?.trim() ?? "";
41985
+ if (toolName === "") return;
41986
+ return {
41987
+ toolName,
41988
+ pattern: pattern === "" ? CATCH_ALL_PATTERN$4 : pattern
41989
+ };
41990
+ }
41991
+ /**
41992
+ * Permissions adapter for the Continue CLI (`cn`).
41993
+ *
41994
+ * Continue reads tool policies from `~/.continue/permissions.yaml`, three
41995
+ * string lists (`allow`, `ask`, `exclude`) of `Tool` / `Tool(pattern)` entries
41996
+ * evaluated exclude-first, then ask, then allow, first match wins. `exclude`
41997
+ * disables the tool. A `Tool(pattern)` entry glob-matches (`*` / `?`) the
41998
+ * tool's primary argument — the shell command for `Bash`, the path for
41999
+ * `Read` / `Edit` / `Write`, the URL for `Fetch`. The file is **global only**:
42000
+ * no project-scoped permissions file is read.
42001
+ *
42002
+ * Mapping (rulesync canonical -> Continue):
42003
+ * - Action: `allow` -> `allow`, `ask` -> `ask`, `deny` -> `exclude`.
42004
+ * - Tool name: `bash` -> `Bash`, `read` -> `Read`, `edit` -> `Edit`,
42005
+ * `write` -> `Write`, `webfetch` -> `Fetch`; any other category passes
42006
+ * through verbatim, and the all-tools `*` category becomes the bare `*`
42007
+ * entry that matches every tool.
42008
+ * - Entry: the catch-all `*` pattern is the bare tool name; every other
42009
+ * pattern is written as `Tool(pattern)`. A pattern holding a parenthesis
42010
+ * cannot be written (the loader would refuse the whole file) and is
42011
+ * reported and skipped.
42012
+ *
42013
+ * The CLI appends the user's own "always allow" decisions to the same file,
42014
+ * so entries naming a tool the canonical config does not manage are preserved
42015
+ * verbatim, the managed tools' entries are rebuilt, and the file is never
42016
+ * deleted.
42017
+ *
42018
+ * @see https://docs.continue.dev/cli/tool-permissions
42019
+ * @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/permissions/permissionsYamlLoader.ts
42020
+ * @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/permissions/permissionChecker.ts
42021
+ */
42022
+ var ContinuePermissions = class ContinuePermissions extends ToolPermissions {
42023
+ constructor(params) {
42024
+ super({
42025
+ ...params,
42026
+ fileContent: params.fileContent ?? ""
42027
+ });
42028
+ }
42029
+ isDeletable() {
42030
+ return false;
42031
+ }
42032
+ /**
42033
+ * `permissions.yaml` is Continue's file: rulesync merges into it when it
42034
+ * exists but does not create one that holds three empty lists — an absent
42035
+ * file and empty lists both mean "no policy, ask for everything".
42036
+ */
42037
+ shouldSkipCreationWhenPayloadEmpty() {
42038
+ return true;
42039
+ }
42040
+ static getSettablePaths(_options) {
42041
+ return {
42042
+ relativeDirPath: CONTINUE_DIR,
42043
+ relativeFilePath: CONTINUE_PERMISSIONS_FILE_NAME
42044
+ };
42045
+ }
42046
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
42047
+ if (!global) throw new Error(CONTINUE_GLOBAL_ONLY_MESSAGE);
42048
+ const paths = ContinuePermissions.getSettablePaths({ global });
42049
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
42050
+ return new ContinuePermissions({
42051
+ outputRoot,
42052
+ relativeDirPath: paths.relativeDirPath,
42053
+ relativeFilePath: paths.relativeFilePath,
42054
+ fileContent,
42055
+ validate,
42056
+ global: true
42057
+ });
42058
+ }
42059
+ static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
42060
+ if (!global) throw new Error(CONTINUE_GLOBAL_ONLY_MESSAGE);
42061
+ const paths = ContinuePermissions.getSettablePaths({ global });
42062
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
42063
+ const existingContent = await readFileContentOrNull(filePath) ?? "";
42064
+ let parsed;
42065
+ try {
42066
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
42067
+ } catch (error) {
42068
+ throw new Error(`Failed to parse existing Continue permissions.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
42069
+ }
42070
+ const existing = parseContinuePermissionLists(isRecord$1(parsed) ? parsed : {});
42071
+ const config = rulesyncPermissions.getJson();
42072
+ const generated = convertRulesyncToContinuePermissions({
42073
+ config,
42074
+ logger
42075
+ });
42076
+ const managedToolNames = managedContinueToolNames(config);
42077
+ const lists = {};
42078
+ for (const key of CONTINUE_PERMISSION_LIST_KEYS) {
42079
+ const preserved = existing[key].filter((entry) => {
42080
+ const parsedEntry = parseContinuePermissionEntry(entry);
42081
+ return parsedEntry === void 0 || !managedToolNames.has(parsedEntry.toolName);
42082
+ });
42083
+ const merged = [.../* @__PURE__ */ new Set([...preserved, ...generated[key]])];
42084
+ if (merged.length > 0) lists[key] = merged;
42085
+ }
42086
+ return new ContinuePermissions({
42087
+ outputRoot,
42088
+ relativeDirPath: paths.relativeDirPath,
42089
+ relativeFilePath: paths.relativeFilePath,
42090
+ fileContent: Object.keys(lists).length === 0 ? "" : dump(lists),
42091
+ validate: true,
42092
+ global: true
42093
+ });
42094
+ }
42095
+ toRulesyncPermissions() {
42096
+ let parsed;
42097
+ try {
42098
+ const content = this.getFileContent();
42099
+ parsed = content.trim() === "" ? {} : loadYaml(content);
42100
+ } catch (error) {
42101
+ throw new Error(`Failed to parse Continue permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
42102
+ }
42103
+ const rulesyncConfig = convertContinuePermissionsToRulesync(parseContinuePermissionLists(isRecord$1(parsed) ? parsed : {}));
42104
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
42105
+ }
42106
+ validate() {
42107
+ return {
42108
+ success: true,
42109
+ error: null
42110
+ };
42111
+ }
42112
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
42113
+ return new ContinuePermissions({
42114
+ outputRoot,
42115
+ relativeDirPath,
42116
+ relativeFilePath,
42117
+ fileContent: "",
42118
+ validate: false,
42119
+ global: true
42120
+ });
42121
+ }
42122
+ };
42123
+ /**
42124
+ * The Continue tool names the canonical config manages, i.e. the names its
42125
+ * categories map to. Entries naming any other tool are the user's (or the
42126
+ * CLI's) and survive a generate.
42127
+ */
42128
+ function managedContinueToolNames(config) {
42129
+ return new Set(Object.keys(config.permission).map((category) => toContinueToolName(category)));
42130
+ }
42131
+ /**
42132
+ * Read the three lists out of a parsed permissions.yaml, tolerating a missing
42133
+ * or malformed list (the CLI itself discards the file in that case).
42134
+ */
42135
+ function parseContinuePermissionLists(parsed) {
42136
+ return {
42137
+ exclude: isStringArray$2(parsed.exclude) ? parsed.exclude : [],
42138
+ ask: isStringArray$2(parsed.ask) ? parsed.ask : [],
42139
+ allow: isStringArray$2(parsed.allow) ? parsed.allow : []
42140
+ };
42141
+ }
42142
+ /**
42143
+ * Convert a rulesync permissions config into the three permissions.yaml lists.
42144
+ */
42145
+ function convertRulesyncToContinuePermissions({ config, logger }) {
42146
+ const lists = {
42147
+ exclude: [],
42148
+ ask: [],
42149
+ allow: []
42150
+ };
42151
+ const actionByEntry = /* @__PURE__ */ new Map();
42152
+ for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
42153
+ if (isPrototypePollutionKey(category)) continue;
42154
+ const toolName = category === "*" ? "*" : toContinueToolName(category);
42155
+ for (const [pattern, action] of Object.entries(rules)) {
42156
+ if (isPrototypePollutionKey(pattern)) continue;
42157
+ if (pattern.includes("(") || pattern.includes(")")) {
42158
+ logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
42159
+ continue;
42160
+ }
42161
+ if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$4) {
42162
+ logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
42163
+ continue;
42164
+ }
42165
+ const entry = buildContinuePermissionEntry(toolName, pattern);
42166
+ const previous = actionByEntry.get(entry);
42167
+ 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.`);
42168
+ actionByEntry.set(entry, action);
42169
+ lists[ACTION_TO_CONTINUE_LIST[action]].push(entry);
42170
+ }
42171
+ }
42172
+ for (const key of CONTINUE_PERMISSION_LIST_KEYS) lists[key] = [...new Set(lists[key])];
42173
+ return lists;
42174
+ }
42175
+ /**
42176
+ * Convert the three permissions.yaml lists back into a rulesync config.
42177
+ */
42178
+ function convertContinuePermissionsToRulesync(lists) {
42179
+ const permission = {};
42180
+ for (const key of CONTINUE_PERMISSION_LIST_KEYS) {
42181
+ const action = CONTINUE_LIST_TO_ACTION[key];
42182
+ for (const entry of lists[key]) {
42183
+ const parsedEntry = parseContinuePermissionEntry(entry);
42184
+ if (parsedEntry === void 0) continue;
42185
+ if (isPrototypePollutionKey(parsedEntry.toolName) || isPrototypePollutionKey(parsedEntry.pattern)) continue;
42186
+ const category = toCanonicalToolName$4(parsedEntry.toolName);
42187
+ permission[category] ??= {};
42188
+ permission[category][parsedEntry.pattern] ??= action;
42189
+ }
42190
+ }
42191
+ return { permission };
42192
+ }
42193
+ //#endregion
41229
42194
  //#region src/features/permissions/copilot-permissions.ts
41230
42195
  /**
41231
42196
  * The flat, dotted VS Code setting keys this adapter manages, one per canonical
@@ -51274,6 +52239,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
51274
52239
  supportsImport: true
51275
52240
  }
51276
52241
  }],
52242
+ ["continue", {
52243
+ class: ContinuePermissions,
52244
+ meta: {
52245
+ supportsProject: false,
52246
+ supportsGlobal: true,
52247
+ supportsImport: true
52248
+ }
52249
+ }],
51277
52250
  ["copilot", {
51278
52251
  class: CopilotPermissions,
51279
52252
  meta: {
@@ -54024,6 +54997,154 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
54024
54997
  }
54025
54998
  };
54026
54999
  //#endregion
55000
+ //#region src/features/skills/continue-skill.ts
55001
+ const ContinueSkillFrontmatterSchema = z.looseObject({
55002
+ name: z.string(),
55003
+ description: z.string()
55004
+ });
55005
+ /**
55006
+ * Represents a Continue skill directory.
55007
+ *
55008
+ * Continue discovers Anthropic-style `<name>/SKILL.md` directories under
55009
+ * `<project>/.continue/skills/` (project scope) and `~/.continue/skills/`
55010
+ * (user scope); it also reads `.claude/skills/`, which the claudecode target
55011
+ * covers. The frontmatter requires `name` (matching the directory name) and
55012
+ * `description`; supporting files next to `SKILL.md` are carried along.
55013
+ *
55014
+ * @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/util/loadMarkdownSkills.ts
55015
+ */
55016
+ var ContinueSkill = class ContinueSkill extends ToolSkill {
55017
+ constructor({ outputRoot = process.cwd(), relativeDirPath = CONTINUE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
55018
+ super({
55019
+ outputRoot,
55020
+ relativeDirPath,
55021
+ dirName,
55022
+ mainFile: {
55023
+ name: SKILL_FILE_NAME,
55024
+ body,
55025
+ frontmatter: { ...frontmatter }
55026
+ },
55027
+ otherFiles,
55028
+ global
55029
+ });
55030
+ if (validate) {
55031
+ const result = this.validate();
55032
+ if (!result.success) throw result.error;
55033
+ }
55034
+ }
55035
+ static getSettablePaths(_options = {}) {
55036
+ return { relativeDirPath: CONTINUE_SKILLS_DIR_PATH };
55037
+ }
55038
+ getFrontmatter() {
55039
+ return ContinueSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
55040
+ }
55041
+ getBody() {
55042
+ return this.mainFile?.body ?? "";
55043
+ }
55044
+ validate() {
55045
+ if (!this.mainFile) return {
55046
+ success: false,
55047
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
55048
+ };
55049
+ const result = ContinueSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
55050
+ if (!result.success) return {
55051
+ success: false,
55052
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
55053
+ };
55054
+ if (result.data.name !== this.getDirName()) return {
55055
+ success: false,
55056
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
55057
+ };
55058
+ return {
55059
+ success: true,
55060
+ error: null
55061
+ };
55062
+ }
55063
+ toRulesyncSkill() {
55064
+ const { name, description, ...continueSection } = this.getFrontmatter();
55065
+ const rulesyncFrontmatter = {
55066
+ name,
55067
+ description,
55068
+ targets: ["*"],
55069
+ ...Object.keys(continueSection).length > 0 && { continue: continueSection }
55070
+ };
55071
+ return new RulesyncSkill({
55072
+ outputRoot: this.outputRoot,
55073
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
55074
+ dirName: this.getDirName(),
55075
+ frontmatter: rulesyncFrontmatter,
55076
+ body: this.getBody(),
55077
+ otherFiles: this.getOtherFiles(),
55078
+ validate: true,
55079
+ global: this.global
55080
+ });
55081
+ }
55082
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
55083
+ const settablePaths = ContinueSkill.getSettablePaths({ global });
55084
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
55085
+ const { name: _sectionName, description: _sectionDescription, ...continueSection } = rulesyncFrontmatter.continue ?? {};
55086
+ const continueFrontmatter = {
55087
+ ...continueSection,
55088
+ name: rulesyncFrontmatter.name,
55089
+ description: rulesyncFrontmatter.description
55090
+ };
55091
+ return new ContinueSkill({
55092
+ outputRoot,
55093
+ relativeDirPath: settablePaths.relativeDirPath,
55094
+ dirName: continueFrontmatter.name,
55095
+ frontmatter: continueFrontmatter,
55096
+ body: rulesyncSkill.getBody(),
55097
+ otherFiles: rulesyncSkill.getOtherFiles(),
55098
+ validate,
55099
+ global
55100
+ });
55101
+ }
55102
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
55103
+ const targets = rulesyncSkill.getFrontmatter().targets;
55104
+ return targets.includes("*") || targets.includes("continue");
55105
+ }
55106
+ static async fromDir(params) {
55107
+ const loaded = await this.loadSkillDirContent({
55108
+ ...params,
55109
+ getSettablePaths: ContinueSkill.getSettablePaths
55110
+ });
55111
+ const result = ContinueSkillFrontmatterSchema.safeParse(loaded.frontmatter);
55112
+ if (!result.success) {
55113
+ const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
55114
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
55115
+ }
55116
+ if (result.data.name !== loaded.dirName) {
55117
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
55118
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
55119
+ }
55120
+ return new ContinueSkill({
55121
+ outputRoot: loaded.outputRoot,
55122
+ relativeDirPath: loaded.relativeDirPath,
55123
+ dirName: loaded.dirName,
55124
+ frontmatter: result.data,
55125
+ body: loaded.body,
55126
+ otherFiles: loaded.otherFiles,
55127
+ validate: true,
55128
+ global: loaded.global
55129
+ });
55130
+ }
55131
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
55132
+ return new ContinueSkill({
55133
+ outputRoot,
55134
+ relativeDirPath,
55135
+ dirName,
55136
+ frontmatter: {
55137
+ name: "",
55138
+ description: ""
55139
+ },
55140
+ body: "",
55141
+ otherFiles: [],
55142
+ validate: false,
55143
+ global
55144
+ });
55145
+ }
55146
+ };
55147
+ //#endregion
54027
55148
  //#region src/features/skills/copilot-skill.ts
54028
55149
  const CopilotSkillFrontmatterSchema = z.looseObject({
54029
55150
  name: z.string(),
@@ -58888,6 +60009,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
58888
60009
  supportsGlobal: true
58889
60010
  }
58890
60011
  }],
60012
+ ["continue", {
60013
+ class: ContinueSkill,
60014
+ meta: {
60015
+ supportsProject: true,
60016
+ supportsSimulated: false,
60017
+ supportsGlobal: true
60018
+ }
60019
+ }],
58891
60020
  ["copilot", {
58892
60021
  class: CopilotSkill,
58893
60022
  meta: {
@@ -67456,6 +68585,263 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
67456
68585
  }
67457
68586
  };
67458
68587
  //#endregion
68588
+ //#region src/features/rules/continue-rule.ts
68589
+ /**
68590
+ * Frontmatter schema for Continue rule files (`.continue/rules/*.md`).
68591
+ * `globs` and `regex` are documented as either a single pattern or a list.
68592
+ * @see https://docs.continue.dev/customize/deep-dives/rules
68593
+ */
68594
+ const ContinueRuleFrontmatterSchema = z.looseObject({
68595
+ name: z.optional(z.string()),
68596
+ description: z.optional(z.string()),
68597
+ globs: z.optional(z.union([z.string(), z.array(z.string())])),
68598
+ regex: z.optional(z.union([z.string(), z.array(z.string())])),
68599
+ alwaysApply: z.optional(z.boolean())
68600
+ });
68601
+ /**
68602
+ * Normalizes the documented `string` / `string[]` shapes to the list form the
68603
+ * adapter works with. Continue treats a scalar as one pattern (no comma
68604
+ * splitting), so the scalar simply becomes a one-element list. An empty list
68605
+ * and an empty string both mean "not set".
68606
+ */
68607
+ function normalizePatternList(value) {
68608
+ if (value === void 0) return;
68609
+ const list = (typeof value === "string" ? [value] : value).filter((pattern) => pattern.trim() !== "");
68610
+ return list.length > 0 ? list : void 0;
68611
+ }
68612
+ /**
68613
+ * A universal glob is redundant on a Continue rule: a `.continue/rules/` file
68614
+ * with no `globs` is already implicitly always-on, and one with `globs` is
68615
+ * only pulled in when a context file matches, so emitting `**\/*` would turn
68616
+ * an always-on rule into a "when any file is in context" rule. Dropped on
68617
+ * generate and mapped back on import.
68618
+ */
68619
+ const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set(["**/*", "*"]);
68620
+ /**
68621
+ * Rule generator for Continue (continuedev/continue), the open-source IDE
68622
+ * extension and CLI (`cn`).
68623
+ *
68624
+ * Rules format:
68625
+ * - {project}/AGENTS.md (root: true) — read from the workspace root and
68626
+ * always applied.
68627
+ * - {project}/.continue/rules/*.md (root: false) — Markdown with optional
68628
+ * `description` / `globs` / `regex` / `alwaysApply` frontmatter. A file
68629
+ * with no `globs`/`regex` is always applied; one with `globs` is applied
68630
+ * when a context file matches; `alwaysApply: true` forces it on and
68631
+ * `alwaysApply: false` makes it depend on `globs`/`regex` only.
68632
+ * - Global: ~/.continue/rules/*.md. Continue does not read `~/AGENTS.md`, so
68633
+ * the global root rule is written to `~/.continue/rules/AGENTS.md`, the
68634
+ * same layout the Roo adapter uses.
68635
+ *
68636
+ * @see https://docs.continue.dev/customize/deep-dives/rules
68637
+ */
68638
+ var ContinueRule = class ContinueRule extends ToolRule {
68639
+ frontmatter;
68640
+ body;
68641
+ static getSettablePaths({ global, excludeToolDir } = {}) {
68642
+ const rulesDirPath = buildToolPath(CONTINUE_DIR, "rules", excludeToolDir);
68643
+ if (global) return {
68644
+ root: {
68645
+ relativeDirPath: rulesDirPath,
68646
+ relativeFilePath: CONTINUE_ROOT_RULE_FILE_NAME
68647
+ },
68648
+ nonRoot: { relativeDirPath: rulesDirPath }
68649
+ };
68650
+ return {
68651
+ root: {
68652
+ relativeDirPath: ".",
68653
+ relativeFilePath: CONTINUE_ROOT_RULE_FILE_NAME
68654
+ },
68655
+ nonRoot: { relativeDirPath: rulesDirPath }
68656
+ };
68657
+ }
68658
+ constructor({ frontmatter, body, ...rest }) {
68659
+ if (rest.validate) {
68660
+ const result = ContinueRuleFrontmatterSchema.safeParse(frontmatter);
68661
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
68662
+ }
68663
+ super({
68664
+ ...rest,
68665
+ fileContent: rest.root ? body : ContinueRule.generateFileContent(body, frontmatter)
68666
+ });
68667
+ this.frontmatter = frontmatter;
68668
+ this.body = body;
68669
+ }
68670
+ static generateFileContent(body, frontmatter) {
68671
+ if (frontmatter.name === void 0 && frontmatter.description === void 0 && frontmatter.globs === void 0 && frontmatter.regex === void 0 && frontmatter.alwaysApply === void 0) return body;
68672
+ return stringifyFrontmatter(body, {
68673
+ name: frontmatter.name,
68674
+ description: frontmatter.description,
68675
+ globs: frontmatter.globs,
68676
+ regex: frontmatter.regex,
68677
+ alwaysApply: frontmatter.alwaysApply
68678
+ });
68679
+ }
68680
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false, relativeDirPath: overrideDirPath }) {
68681
+ const paths = this.getSettablePaths({ global });
68682
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
68683
+ if (relativeFilePath === paths.root.relativeFilePath && rootDirPath === paths.root.relativeDirPath) {
68684
+ const fileContent = await readFileContent(join(outputRoot, rootDirPath, paths.root.relativeFilePath));
68685
+ return new ContinueRule({
68686
+ outputRoot,
68687
+ relativeDirPath: rootDirPath,
68688
+ relativeFilePath: paths.root.relativeFilePath,
68689
+ frontmatter: {},
68690
+ body: fileContent.trim(),
68691
+ validate,
68692
+ root: true
68693
+ });
68694
+ }
68695
+ const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
68696
+ const filePath = join(outputRoot, relativePath);
68697
+ const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
68698
+ const result = ContinueRuleFrontmatterSchema.safeParse(frontmatter);
68699
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
68700
+ return new ContinueRule({
68701
+ outputRoot,
68702
+ relativeDirPath: paths.nonRoot.relativeDirPath,
68703
+ relativeFilePath,
68704
+ frontmatter: result.data,
68705
+ body: content.trim(),
68706
+ validate,
68707
+ root: false
68708
+ });
68709
+ }
68710
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
68711
+ const paths = this.getSettablePaths({ global });
68712
+ const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath === paths.root.relativeDirPath;
68713
+ return new ContinueRule({
68714
+ outputRoot,
68715
+ relativeDirPath,
68716
+ relativeFilePath,
68717
+ frontmatter: {},
68718
+ body: "",
68719
+ validate: false,
68720
+ root: isRoot
68721
+ });
68722
+ }
68723
+ /**
68724
+ * Resolves the `globs` to emit. A universal glob is dropped because a rule
68725
+ * without `globs` is already always-on in Continue (see `UNIVERSAL_GLOBS`),
68726
+ * and every glob is dropped alongside `alwaysApply: true`, which forces the
68727
+ * rule on regardless of them.
68728
+ */
68729
+ static resolveContinueGlobs({ continueGlobs, parentGlobs, alwaysApply }) {
68730
+ if (alwaysApply === true) return;
68731
+ const targetGlobs = continueGlobs ?? parentGlobs;
68732
+ if (!targetGlobs || targetGlobs.length === 0) return;
68733
+ if (targetGlobs.every((glob) => UNIVERSAL_GLOBS$1.has(glob.trim()))) return;
68734
+ return targetGlobs;
68735
+ }
68736
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
68737
+ const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
68738
+ const root = rulesyncFrontmatter.root ?? false;
68739
+ const paths = this.getSettablePaths({ global });
68740
+ const body = rulesyncRule.getBody();
68741
+ if (root) return new ContinueRule({
68742
+ outputRoot,
68743
+ frontmatter: {},
68744
+ body,
68745
+ relativeDirPath: paths.root.relativeDirPath,
68746
+ relativeFilePath: paths.root.relativeFilePath,
68747
+ validate,
68748
+ root
68749
+ });
68750
+ const continueFrontmatter = rulesyncFrontmatter.continue;
68751
+ const continueRuleFrontmatter = {
68752
+ name: continueFrontmatter?.name,
68753
+ description: continueFrontmatter?.description ?? rulesyncFrontmatter.description,
68754
+ globs: ContinueRule.resolveContinueGlobs({
68755
+ continueGlobs: normalizePatternList(continueFrontmatter?.globs),
68756
+ parentGlobs: rulesyncFrontmatter.globs,
68757
+ alwaysApply: continueFrontmatter?.alwaysApply
68758
+ }),
68759
+ regex: normalizePatternList(continueFrontmatter?.regex),
68760
+ alwaysApply: continueFrontmatter?.alwaysApply
68761
+ };
68762
+ return new ContinueRule({
68763
+ outputRoot,
68764
+ frontmatter: continueRuleFrontmatter,
68765
+ body,
68766
+ relativeDirPath: paths.nonRoot.relativeDirPath,
68767
+ relativeFilePath: rulesyncRule.getRelativeFilePath(),
68768
+ validate,
68769
+ root
68770
+ });
68771
+ }
68772
+ toRulesyncRule() {
68773
+ const targets = ["*"];
68774
+ if (this.isRoot()) {
68775
+ const rulesyncFrontmatter = {
68776
+ targets,
68777
+ root: true,
68778
+ description: this.description,
68779
+ globs: ["**/*"]
68780
+ };
68781
+ return new RulesyncRule({
68782
+ outputRoot: this.getOutputRoot(),
68783
+ frontmatter: rulesyncFrontmatter,
68784
+ body: this.body,
68785
+ relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
68786
+ relativeFilePath: RULESYNC_OVERVIEW_FILE_NAME,
68787
+ validate: true
68788
+ });
68789
+ }
68790
+ const sourceGlobs = normalizePatternList(this.frontmatter.globs) ?? [];
68791
+ const regex = normalizePatternList(this.frontmatter.regex);
68792
+ const isAlways = this.frontmatter.alwaysApply !== false;
68793
+ const globs = sourceGlobs.length === 0 && isAlways ? ["**/*"] : sourceGlobs;
68794
+ const { name, alwaysApply } = this.frontmatter;
68795
+ const hasContinueSection = name !== void 0 || alwaysApply !== void 0 || regex !== void 0;
68796
+ const rulesyncFrontmatter = {
68797
+ targets,
68798
+ root: false,
68799
+ description: this.frontmatter.description,
68800
+ globs,
68801
+ ...hasContinueSection && { continue: {
68802
+ ...name !== void 0 && { name },
68803
+ alwaysApply,
68804
+ regex
68805
+ } }
68806
+ };
68807
+ return new RulesyncRule({
68808
+ outputRoot: this.getOutputRoot(),
68809
+ frontmatter: rulesyncFrontmatter,
68810
+ body: this.body,
68811
+ relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
68812
+ relativeFilePath: this.getRelativeFilePath(),
68813
+ validate: true
68814
+ });
68815
+ }
68816
+ validate() {
68817
+ if (!this.frontmatter) return {
68818
+ success: true,
68819
+ error: null
68820
+ };
68821
+ const result = ContinueRuleFrontmatterSchema.safeParse(this.frontmatter);
68822
+ if (result.success) return {
68823
+ success: true,
68824
+ error: null
68825
+ };
68826
+ return {
68827
+ success: false,
68828
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
68829
+ };
68830
+ }
68831
+ getFrontmatter() {
68832
+ return this.frontmatter;
68833
+ }
68834
+ getBody() {
68835
+ return this.body;
68836
+ }
68837
+ static isTargetedByRulesyncRule(rulesyncRule) {
68838
+ return this.isTargetedByRulesyncRuleDefault({
68839
+ rulesyncRule,
68840
+ toolTarget: "continue"
68841
+ });
68842
+ }
68843
+ };
68844
+ //#endregion
67459
68845
  //#region src/features/rules/copilot-rule.ts
67460
68846
  const CopilotRuleFrontmatterSchema = z.looseObject({
67461
68847
  description: z.optional(z.string()),
@@ -71537,6 +72923,14 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
71537
72923
  collisionPolicy: "fold"
71538
72924
  }
71539
72925
  }],
72926
+ ["continue", {
72927
+ class: ContinueRule,
72928
+ meta: {
72929
+ extension: "md",
72930
+ supportsGlobal: true,
72931
+ ruleDiscoveryMode: "auto"
72932
+ }
72933
+ }],
71540
72934
  ["copilot", {
71541
72935
  class: CopilotRule,
71542
72936
  meta: {
@@ -75025,4 +76419,4 @@ async function importChecksCore(params) {
75025
76419
  //#endregion
75026
76420
  export { RulesyncCheck as $, writeFileContent as $t, CLAUDECODE_SKILLS_DIR_PATH as A, RULESYNC_SKILLS_RELATIVE_DIR_PATH as An, directoryExists as At, RulesyncSkill as B, truncateText as Bn, listSubdirectoryNames as Bt, ChecksProcessor as C, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Cn, ErrorCodes as Ct, CLAUDECODE_LOCAL_RULE_FILE_NAME as D, RULESYNC_PERMISSIONS_SCHEMA_URL as Dn, assertWritablePathInsideRoot as Dt, CLAUDECODE_DIR as E, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as En, assertTreeContainsNoSymlinks as Et, AUGMENTCODE_DIR as F, parseCommaSeparatedList as Fn, isFileNotFoundError as Ft, RulesyncMcp as G, stripControlCharactersKeepingLineFeeds as Gn, removeDirectoryStrict as Gt, RulesyncRule as H, hasEnclosingMarkOutsideKeycap as Hn, readFileContent as Ht, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as I, ALL_FEATURES as In, isFileSystemError as It, getRulesyncSourceCandidates as J, removeTempDirectory as Jt, RulesyncIgnore as K, stripHiddenCharacters as Kn, removeFile as Kt, getLocalSkillDirNames as L, ALL_FEATURES_WITH_WILDCARD as Ln, isSymlink as Lt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as M, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Mn, fileExists as Mt, caseFoldIdentity as N, RULESYNC_USER_CONFIG_DIR_NAME as Nn, getFileSize as Nt, CLAUDECODE_MEMORIES_DIR_NAME as O, RULESYNC_RELATIVE_DIR_PATH as On, checkPathTraversal as Ot, groupSpellingsByCaseFoldedIdentity as P, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Pn, getHomeDirectory as Pt, RulesyncCommandFrontmatterSchema as Q, writeFileBuffer as Qt, RulesyncSubagent as R, DEPRECATED_FEATURE_REPLACEMENTS as Rn, listDirectoryEntryNames as Rt, QWENCODE_LOCAL_RULE_FILE_NAME as S, RULESYNC_MCP_SCHEMA_URL as Sn, CLIError as St, CODEXCLI_DIR as T, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Tn, assertDirectoryIfExists as Tt, RulesyncRuleFrontmatterSchema as U, quoteForLog as Un, readFileContentOrNull as Ut, RulesyncSkillFrontmatterSchema as V, hasDeceptiveHiddenCharacters as Vn, pathEscapesRoot as Vt, RulesyncPermissions as W, stripControlCharacters as Wn, removeDirectory as Wt, parseJsonc as X, runWithDirectoryRollback as Xt, resolveRulesyncSourceWritePath as Y, resolvePath as Yt, RulesyncCommand as Z, toPosixPath as Zt, IgnoreProcessor as _, RULESYNC_IGNORE_RELATIVE_FILE_PATH as _n, fallbackLogger as _t, getProcessorRegistryEntry as a, MAX_FILE_SIZE as an, ConfigResolver as at, CommandsProcessor as b, RULESYNC_MCP_LEGACY_FILE_NAME as bn, resetRunWarningState as bt, RulesProcessor as c, RULESYNC_CHECKS_RELATIVE_DIR_PATH as cn, CONFLICTING_TARGET_PAIRS as ct, CODEBUDDY_DIR as d, RULESYNC_CONFIG_SCHEMA_URL as dn, SourceEntrySchema as dt, ALL_TOOL_TARGETS as en, RulesyncCheckFrontmatterSchema as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as fn, assertTargetsFeaturesExclusive as ft, McpProcessor as g, RULESYNC_HOOKS_RELATIVE_FILE_PATH as gn, WarningCollectingLogger as gt, shortenToWidth as h, RULESYNC_HOOKS_LEGACY_FILE_NAME as hn, JsonLogger as ht, inspectInputRoots as i, CURATED_RULES_FEATURE_SUBDIR as in, SKILL_FILE_NAME as it, FACTORYDROID_DIR as j, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as jn, ensureDir as jt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as k, RULESYNC_RULES_RELATIVE_DIR_PATH as kn, createTempDirectory as kt, SubagentsProcessor as l, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as ln, ConfigFileSchema as lt, displayWidthOf as m, RULESYNC_HOOKS_FILE_NAME as mn, ConsoleLogger as mt, formatSourceLoadFailure as n, PACKAGING_TOOL_TARGETS as nn, loadYaml as nt, convertFromTool as o, RULESYNC_AIIGNORE_FILE_NAME as on, mergeInputRootConfigs as ot, ELLIPSIS_WIDTH as p, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as pn, findControlCharacter as pt, RulesyncHooks as q, removeFileStrict as qt, generate as r, ToolTargetSchema as rn, SHARED_USER_MANAGED_CONFIG_PATHS as rt, isPackagingToolTarget as s, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as sn, resolveEffectiveInputRoots as st, importFromTool as t, ALL_TOOL_TARGETS_WITH_WILDCARD as tn, stringifyFrontmatter as tt, SkillsProcessor as u, RULESYNC_CONFIG_RELATIVE_FILE_PATH as un, GITIGNORE_DESTINATION_KEY as ut, CRUSH_LOCAL_RULE_FILE_NAME as v, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as vn, warnOnConflictingFlags as vt, CODEXCLI_BASH_RULES_FILE_NAME as w, RULESYNC_PERMISSIONS_FILE_NAME as wn, applyFileMode as wt, QWENCODE_DIR as x, RULESYNC_MCP_RELATIVE_FILE_PATH as xn, withWarnOnceScope as xt, HooksProcessor as y, RULESYNC_MCP_FILE_NAME as yn, withFallbackLoggerTarget as yt, RulesyncSubagentFrontmatterSchema as z, formatError as zn, listFilePathsRecursively as zt };
75027
76421
 
75028
- //# sourceMappingURL=import-D2GhBXJV.js.map
76422
+ //# sourceMappingURL=import-D5n_V39C.js.map