rulesync 16.33.0 → 16.34.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
+ "commandcode",
481
482
  "continue",
482
483
  "copilot",
483
484
  "copilotcli",
@@ -554,6 +555,7 @@ const mcpProcessorToolTargetTuple = [
554
555
  "claudecode-legacy",
555
556
  "cline",
556
557
  "codexcli",
558
+ "commandcode",
557
559
  "continue",
558
560
  "copilot",
559
561
  "copilotcli",
@@ -597,6 +599,7 @@ const commandsProcessorToolTargetTuple = [
597
599
  "claudecode-legacy",
598
600
  "cline",
599
601
  "codexcli",
602
+ "commandcode",
600
603
  "continue",
601
604
  "copilot",
602
605
  "cursor",
@@ -634,6 +637,7 @@ const subagentsProcessorToolTargetTuple = [
634
637
  "claudecode-legacy",
635
638
  "cline",
636
639
  "codexcli",
640
+ "commandcode",
637
641
  "copilot",
638
642
  "copilotcli",
639
643
  "cortexcode",
@@ -675,6 +679,7 @@ const skillsProcessorToolTargetTuple = [
675
679
  "claudecode-legacy",
676
680
  "cline",
677
681
  "codexcli",
682
+ "commandcode",
678
683
  "continue",
679
684
  "copilot",
680
685
  "copilotcli",
@@ -722,6 +727,7 @@ const hooksProcessorToolTargetTuple = [
722
727
  "claudecode",
723
728
  "claudecode-plugin",
724
729
  "codexcli",
730
+ "commandcode",
725
731
  "continue",
726
732
  "copilot",
727
733
  "copilotcli",
@@ -756,6 +762,7 @@ const permissionsProcessorToolTargetTuple = [
756
762
  "claudecode",
757
763
  "cline",
758
764
  "codexcli",
765
+ "commandcode",
759
766
  "continue",
760
767
  "copilot",
761
768
  "copilotcli",
@@ -3270,6 +3277,7 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
3270
3277
  ".claude/settings.json",
3271
3278
  ".claude/settings.local.json",
3272
3279
  ".codex/config.toml",
3280
+ ".commandcode/settings.json",
3273
3281
  ".continue/settings.json",
3274
3282
  ".copilot/settings.json",
3275
3283
  ".github/copilot/settings.json",
@@ -4927,6 +4935,30 @@ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
4927
4935
  };
4928
4936
  const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4929
4937
  /**
4938
+ * Hook events supported by Command Code.
4939
+ *
4940
+ * Command Code reads hooks from the `hooks` key of `.commandcode/settings.json`
4941
+ * (project) and `~/.commandcode/settings.json` (user) in the Claude-Code shape.
4942
+ * Four events are documented: PreToolUse, PostToolUse, Stop and SessionStart.
4943
+ * Only `command` hooks exist; `timeout` is in seconds (default 30, max 600)
4944
+ * and `$COMMANDCODE_PROJECT_DIR` resolves to the project root.
4945
+ *
4946
+ * @see https://commandcode.ai/docs/hooks
4947
+ */
4948
+ const COMMANDCODE_HOOK_EVENTS = [
4949
+ "preToolUse",
4950
+ "postToolUse",
4951
+ "stop",
4952
+ "sessionStart"
4953
+ ];
4954
+ const CANONICAL_TO_COMMANDCODE_EVENT_NAMES = {
4955
+ preToolUse: "PreToolUse",
4956
+ postToolUse: "PostToolUse",
4957
+ stop: "Stop",
4958
+ sessionStart: "SessionStart"
4959
+ };
4960
+ const COMMANDCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_COMMANDCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4961
+ /**
4930
4962
  * Hook events supported by the Continue CLI (`cn`).
4931
4963
  *
4932
4964
  * Continue reads a Claude-Code-compatible `hooks` key from
@@ -5135,6 +5167,7 @@ const HooksConfigSchema = z.looseObject({
5135
5167
  crush: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5136
5168
  bob: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5137
5169
  cortexcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5170
+ commandcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5138
5171
  continue: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5139
5172
  tabnine: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5140
5173
  qwencode: z.optional(z.looseObject({
@@ -6154,6 +6187,7 @@ const RulesyncMcpFileSchema = z.looseObject({
6154
6187
  claudecode: z.optional(toolScopedMcpSchema),
6155
6188
  cline: z.optional(toolScopedMcpSchema),
6156
6189
  codexcli: z.optional(toolScopedMcpSchema),
6190
+ commandcode: z.optional(toolScopedMcpSchema),
6157
6191
  continue: z.optional(toolScopedMcpSchema),
6158
6192
  copilot: z.optional(toolScopedMcpSchema),
6159
6193
  copilotcli: z.optional(toolScopedMcpSchema),
@@ -6549,12 +6583,16 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
6549
6583
  * Targets that share one output file resolve identically so the shared
6550
6584
  * file's content never depends on which of them generates last — see
6551
6585
  * `resolveMcpTarget` for the alias groups (kiro trio, claudecode/-legacy,
6552
- * and the Antigravity pair).
6586
+ * the Antigravity pair, and the project-scope Claude Code / Command Code
6587
+ * pair, which is why the scope is part of the resolution).
6553
6588
  *
6554
6589
  * Returns the same instance when neither mechanism is used.
6555
6590
  */
6556
- forTarget({ toolTarget, logger }) {
6557
- const { blockKeys, acceptedTargetNames } = resolveMcpTarget({ toolTarget });
6591
+ forTarget({ toolTarget, global = false, logger }) {
6592
+ const { blockKeys, acceptedTargetNames } = resolveMcpTarget({
6593
+ toolTarget,
6594
+ global
6595
+ });
6558
6596
  const json = this.json;
6559
6597
  const sharedServers = this.json.mcpServers ?? {};
6560
6598
  const serverNamesWithTargets = Object.entries(sharedServers).filter(([, serverConfig]) => serverConfig.targets !== void 0).map(([serverName]) => serverName);
@@ -6641,9 +6679,24 @@ const MCP_IGNORED_ALIAS_SOURCE_KEYS = Object.keys(MCP_BLOCK_KEY_ALIASES);
6641
6679
  * `config`) — so both targets always apply both blocks in a fixed order
6642
6680
  * (`antigravity-ide` first, `antigravity-cli` second — the CLI block wins
6643
6681
  * per server on conflict).
6644
- */
6645
- function resolveMcpTarget({ toolTarget }) {
6646
- if (toolTarget === "claudecode" || toolTarget === "claudecode-legacy") return {
6682
+ * - `claudecode` (and its legacy alias) and `commandcode` share the root
6683
+ * `.mcp.json` in PROJECT mode only (their global files differ:
6684
+ * `~/.claude.json` vs `~/.commandcode/mcp.json`), so in project mode the
6685
+ * three apply the `claudecode` and `commandcode` blocks in that fixed order
6686
+ * (the `commandcode` block wins per server on conflict); in global mode
6687
+ * each reads its own block.
6688
+ */
6689
+ function resolveMcpTarget({ toolTarget, global }) {
6690
+ const isClaudecode = toolTarget === "claudecode" || toolTarget === "claudecode-legacy";
6691
+ if (!global && (isClaudecode || toolTarget === "commandcode")) return {
6692
+ blockKeys: ["claudecode", "commandcode"],
6693
+ acceptedTargetNames: /* @__PURE__ */ new Set([
6694
+ "claudecode",
6695
+ "claudecode-legacy",
6696
+ "commandcode"
6697
+ ])
6698
+ };
6699
+ if (isClaudecode) return {
6647
6700
  blockKeys: ["claudecode"],
6648
6701
  acceptedTargetNames: /* @__PURE__ */ new Set(["claudecode", "claudecode-legacy"])
6649
6702
  };
@@ -7808,6 +7861,7 @@ const PermissionsConfigSchema = z.looseObject({
7808
7861
  "antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
7809
7862
  continue: z.optional(CanonicalPermissionsOverrideSchema),
7810
7863
  copilot: z.optional(CanonicalPermissionsOverrideSchema),
7864
+ commandcode: z.optional(CanonicalPermissionsOverrideSchema),
7811
7865
  copilotcli: z.optional(CanonicalPermissionsOverrideSchema),
7812
7866
  crush: z.optional(CanonicalPermissionsOverrideSchema),
7813
7867
  goose: z.optional(CanonicalPermissionsOverrideSchema),
@@ -9530,6 +9584,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
9530
9584
  "user-invocable": z.optional(z.boolean()),
9531
9585
  "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
9532
9586
  })),
9587
+ commandcode: z.optional(z.looseObject({})),
9533
9588
  cortexcode: z.optional(z.looseObject({})),
9534
9589
  continue: z.optional(z.looseObject({})),
9535
9590
  tabnine: z.optional(z.looseObject({})),
@@ -13141,6 +13196,20 @@ const SHARED_CONFIG_OWNERSHIP = {
13141
13196
  ownedKeys: ["hooks"]
13142
13197
  } }
13143
13198
  },
13199
+ ".commandcode/settings.json": {
13200
+ format: "json",
13201
+ invalidRootPolicy: "error",
13202
+ features: {
13203
+ hooks: {
13204
+ kind: "replace-owned-keys",
13205
+ ownedKeys: ["hooks"]
13206
+ },
13207
+ permissions: {
13208
+ kind: "replace-owned-keys",
13209
+ ownedKeys: ["permissions"]
13210
+ }
13211
+ }
13212
+ },
13144
13213
  ".cortex/settings.json": {
13145
13214
  format: "json",
13146
13215
  invalidRootPolicy: "error",
@@ -15342,6 +15411,94 @@ var CodexcliCommand = class CodexcliCommand extends ToolCommand {
15342
15411
  }
15343
15412
  };
15344
15413
  //#endregion
15414
+ //#region src/constants/commandcode-paths.ts
15415
+ const COMMANDCODE_DIR = ".commandcode";
15416
+ const COMMANDCODE_RULE_FILE_NAME = "AGENTS.md";
15417
+ const COMMANDCODE_SETTINGS_FILE_NAME = "settings.json";
15418
+ const COMMANDCODE_PROJECT_MCP_FILE_NAME = ".mcp.json";
15419
+ const COMMANDCODE_GLOBAL_MCP_FILE_NAME = "mcp.json";
15420
+ const COMMANDCODE_COMMANDS_DIR_PATH = join(COMMANDCODE_DIR, "commands");
15421
+ const COMMANDCODE_AGENTS_DIR_PATH = join(COMMANDCODE_DIR, "agents");
15422
+ const COMMANDCODE_SKILLS_DIR_PATH = join(COMMANDCODE_DIR, "skills");
15423
+ //#endregion
15424
+ //#region src/features/commands/commandcode-command.ts
15425
+ /**
15426
+ * Custom slash command for Command Code.
15427
+ *
15428
+ * Command Code reads Markdown files from `.commandcode/commands/` (project)
15429
+ * and `~/.commandcode/commands/` (user), naming each command after the file's
15430
+ * basename; subdirectories only group the files. The file's full trimmed
15431
+ * body is the prompt that runs — YAML frontmatter is not stripped, only
15432
+ * skipped when the slash menu picks a summary line — so rulesync writes the
15433
+ * bare body. On import a hand-written file's frontmatter block is dropped
15434
+ * (Command Code would send it as prompt text; a rulesync command carries its
15435
+ * own frontmatter) and only the body is kept.
15436
+ *
15437
+ * @see https://commandcode.ai/docs/custom-slash-commands
15438
+ */
15439
+ var CommandcodeCommand = class CommandcodeCommand extends ToolCommand {
15440
+ static getSettablePaths(_options = {}) {
15441
+ return { relativeDirPath: COMMANDCODE_COMMANDS_DIR_PATH };
15442
+ }
15443
+ toRulesyncCommand() {
15444
+ return new RulesyncCommand({
15445
+ outputRoot: process.cwd(),
15446
+ frontmatter: { targets: ["*"] },
15447
+ body: this.getFileContent(),
15448
+ relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
15449
+ relativeFilePath: this.relativeFilePath,
15450
+ fileContent: this.getFileContent(),
15451
+ validate: true
15452
+ });
15453
+ }
15454
+ static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true }) {
15455
+ const paths = this.getSettablePaths();
15456
+ return new CommandcodeCommand({
15457
+ outputRoot,
15458
+ fileContent: rulesyncCommand.getBody(),
15459
+ relativeDirPath: paths.relativeDirPath,
15460
+ relativeFilePath: rulesyncCommand.getRelativeFilePath(),
15461
+ validate
15462
+ });
15463
+ }
15464
+ validate() {
15465
+ return {
15466
+ success: true,
15467
+ error: null
15468
+ };
15469
+ }
15470
+ getBody() {
15471
+ return this.getFileContent();
15472
+ }
15473
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
15474
+ return this.isTargetedByRulesyncCommandDefault({
15475
+ rulesyncCommand,
15476
+ toolTarget: "commandcode"
15477
+ });
15478
+ }
15479
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
15480
+ const paths = this.getSettablePaths();
15481
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
15482
+ const { body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
15483
+ return new CommandcodeCommand({
15484
+ outputRoot,
15485
+ relativeDirPath: paths.relativeDirPath,
15486
+ relativeFilePath,
15487
+ fileContent: content.trim(),
15488
+ validate
15489
+ });
15490
+ }
15491
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
15492
+ return new CommandcodeCommand({
15493
+ outputRoot,
15494
+ relativeDirPath,
15495
+ relativeFilePath,
15496
+ fileContent: "",
15497
+ validate: false
15498
+ });
15499
+ }
15500
+ };
15501
+ //#endregion
15345
15502
  //#region src/constants/continue-paths.ts
15346
15503
  const CONTINUE_DIR = ".continue";
15347
15504
  const CONTINUE_ROOT_RULE_FILE_NAME = "AGENTS.md";
@@ -19585,6 +19742,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
19585
19742
  supportsSubdirectory: false
19586
19743
  }
19587
19744
  }],
19745
+ ["commandcode", {
19746
+ class: CommandcodeCommand,
19747
+ meta: {
19748
+ extension: "md",
19749
+ supportsProject: true,
19750
+ supportsGlobal: true,
19751
+ isSimulated: false,
19752
+ supportsSubdirectory: true
19753
+ }
19754
+ }],
19588
19755
  ["continue", {
19589
19756
  class: ContinueCommand,
19590
19757
  meta: {
@@ -22955,6 +23122,132 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
22955
23122
  }
22956
23123
  };
22957
23124
  //#endregion
23125
+ //#region src/features/hooks/commandcode-hooks.ts
23126
+ const COMMANDCODE_CONVERTER_CONFIG = {
23127
+ supportedEvents: COMMANDCODE_HOOK_EVENTS,
23128
+ canonicalToToolEventNames: CANONICAL_TO_COMMANDCODE_EVENT_NAMES,
23129
+ toolToCanonicalEventNames: COMMANDCODE_TO_CANONICAL_EVENT_NAMES,
23130
+ projectDirVar: "$COMMANDCODE_PROJECT_DIR",
23131
+ prefixDotRelativeCommandsOnly: true,
23132
+ noMatcherEvents: /* @__PURE__ */ new Set(["stop", "sessionStart"]),
23133
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"])
23134
+ };
23135
+ /**
23136
+ * Single spelling of the settings codec/policy: fail closed on an unparseable
23137
+ * root rather than replacing the user's Command Code settings with generated
23138
+ * output.
23139
+ */
23140
+ function parseCommandcodeSettings$1(fileContent, filePath) {
23141
+ return parseSharedConfig({
23142
+ format: "json",
23143
+ fileContent,
23144
+ filePath,
23145
+ invalidRootPolicy: "error"
23146
+ });
23147
+ }
23148
+ /**
23149
+ * Command Code hooks.
23150
+ *
23151
+ * Hooks live under the top-level `hooks` key of `<project>/.commandcode/settings.json`
23152
+ * (project scope) and `~/.commandcode/settings.json` (user scope), in the
23153
+ * Claude-Code shape: `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{
23154
+ * "type": "command", "command": "...", "timeout"?: <seconds> }] }] }`. Both
23155
+ * files also hold settings rulesync does not own (`permissions`, `defaultMode`,
23156
+ * ...), so generation merges the `hooks` key into the existing file (see
23157
+ * `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
23158
+ *
23159
+ * @see https://commandcode.ai/docs/hooks
23160
+ * @see https://commandcode.ai/docs/settings
23161
+ */
23162
+ var CommandcodeHooks = class CommandcodeHooks extends ToolHooks {
23163
+ constructor(params) {
23164
+ super({
23165
+ ...params,
23166
+ fileContent: params.fileContent ?? "{}"
23167
+ });
23168
+ }
23169
+ isDeletable() {
23170
+ return false;
23171
+ }
23172
+ static getSettablePaths(_options = {}) {
23173
+ return {
23174
+ relativeDirPath: COMMANDCODE_DIR,
23175
+ relativeFilePath: COMMANDCODE_SETTINGS_FILE_NAME
23176
+ };
23177
+ }
23178
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
23179
+ const paths = CommandcodeHooks.getSettablePaths({ global });
23180
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
23181
+ return new CommandcodeHooks({
23182
+ outputRoot,
23183
+ relativeDirPath: paths.relativeDirPath,
23184
+ relativeFilePath: paths.relativeFilePath,
23185
+ fileContent,
23186
+ validate
23187
+ });
23188
+ }
23189
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
23190
+ const paths = CommandcodeHooks.getSettablePaths({ global });
23191
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
23192
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
23193
+ const config = rulesyncHooks.getJson();
23194
+ const hooks = canonicalToToolHooks({
23195
+ config,
23196
+ toolOverrideHooks: config.commandcode?.hooks,
23197
+ converterConfig: COMMANDCODE_CONVERTER_CONFIG,
23198
+ logger
23199
+ });
23200
+ const fileContent = applySharedConfigPatch({
23201
+ fileKey: sharedConfigFileKey(paths),
23202
+ feature: "hooks",
23203
+ existingContent,
23204
+ patch: { hooks },
23205
+ filePath,
23206
+ logger
23207
+ });
23208
+ return new CommandcodeHooks({
23209
+ outputRoot,
23210
+ relativeDirPath: paths.relativeDirPath,
23211
+ relativeFilePath: paths.relativeFilePath,
23212
+ fileContent,
23213
+ validate
23214
+ });
23215
+ }
23216
+ toRulesyncHooks({ logger } = {}) {
23217
+ const configPath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
23218
+ let settings;
23219
+ try {
23220
+ settings = parseCommandcodeSettings$1(this.getFileContent(), configPath);
23221
+ } catch (error) {
23222
+ throw new Error(`Failed to parse Command Code hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
23223
+ }
23224
+ const hooks = toolHooksToCanonical({
23225
+ logger,
23226
+ hooks: settings.hooks,
23227
+ converterConfig: COMMANDCODE_CONVERTER_CONFIG
23228
+ });
23229
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
23230
+ hooks,
23231
+ overrideKey: "commandcode"
23232
+ }), null, 2) });
23233
+ }
23234
+ validate() {
23235
+ return {
23236
+ success: true,
23237
+ error: null
23238
+ };
23239
+ }
23240
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
23241
+ return new CommandcodeHooks({
23242
+ outputRoot,
23243
+ relativeDirPath,
23244
+ relativeFilePath,
23245
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
23246
+ validate: false
23247
+ });
23248
+ }
23249
+ };
23250
+ //#endregion
22958
23251
  //#region src/features/hooks/continue-hooks.ts
22959
23252
  const CONTINUE_SPEC = {
22960
23253
  displayName: "Continue",
@@ -28074,6 +28367,18 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
28074
28367
  supportedHookTypes: ["command"],
28075
28368
  supportsMatcher: true
28076
28369
  }],
28370
+ ["commandcode", {
28371
+ class: CommandcodeHooks,
28372
+ meta: {
28373
+ supportsProject: true,
28374
+ supportsGlobal: true,
28375
+ supportsImport: true
28376
+ },
28377
+ supportedEvents: COMMANDCODE_HOOK_EVENTS,
28378
+ supportedHookTypes: ["command"],
28379
+ supportsMatcher: true,
28380
+ matcherEvents: ["preToolUse", "postToolUse"]
28381
+ }],
28077
28382
  ["copilot", {
28078
28383
  class: CopilotHooks,
28079
28384
  meta: {
@@ -31931,6 +32236,254 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
31931
32236
  }
31932
32237
  };
31933
32238
  //#endregion
32239
+ //#region src/features/mcp/commandcode-mcp.ts
32240
+ /**
32241
+ * Parse a Command Code MCP file. The project file is the `.mcp.json` other
32242
+ * agents (Claude Code among them) share, so a hand-written file is read as
32243
+ * JSONC to tolerate comments and trailing commas; malformed content or a
32244
+ * non-object root (`null`, an array, a scalar) fails closed rather than being
32245
+ * spread into the regenerated file.
32246
+ */
32247
+ function parseCommandcodeMcpConfig({ fileContent, relativePath }) {
32248
+ let parsed;
32249
+ try {
32250
+ parsed = parseJsonc(fileContent);
32251
+ } catch (error) {
32252
+ throw new Error(`Failed to parse Command Code MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
32253
+ }
32254
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Command Code MCP config at ${relativePath}: expected a JSON object at the root`);
32255
+ return parsed;
32256
+ }
32257
+ /**
32258
+ * The remote transport Command Code reads a server as. Its loader accepts
32259
+ * `http` (streamable HTTP) and `sse` for a `url` server; a bare `url` defaults
32260
+ * to `http`, and the canonical `streamable-http` spelling is folded into it.
32261
+ * A `ws(s)://` URL or any other stated transport has no Command Code
32262
+ * equivalent, so `undefined` tells the caller to skip the server.
32263
+ * @see https://commandcode.ai/docs/mcp
32264
+ */
32265
+ function asCommandcodeRemoteTransport(stated, url) {
32266
+ if (stated === "sse") return "sse";
32267
+ if (stated === "http" || stated === "streamable-http") return "http";
32268
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
32269
+ }
32270
+ /**
32271
+ * Convert the canonical server map to the shape Command Code's config schema
32272
+ * documents (used for the global `~/.commandcode/mcp.json` only — the project
32273
+ * `.mcp.json` is shared with Claude Code and written pass-through, see the
32274
+ * class doc): a remote server is `{ transport, url, headers?, env?, oauth? }`
32275
+ * and a stdio server is `{ transport: "stdio", command, args?, env? }`, each
32276
+ * with an optional `enabled` flag. The documented spelling is `transport`
32277
+ * (`type` is only accepted as an alias on read), so that is what is written,
32278
+ * and only the documented keys are emitted — `timeout`, rulesync-only fields
32279
+ * and anything else is left out. The canonical `disabled: true` becomes
32280
+ * Command Code's native `enabled: false` (the rulesync-source-only `enabled`
32281
+ * filter never reaches this function: `getMcpServers()` drops such a server).
32282
+ *
32283
+ * A server Command Code drops at load time — no transport at all, a remote
32284
+ * transport without a URL, a WebSocket URL, or a stdio entry without a
32285
+ * command — is skipped with a warning rather than written in a form the tool
32286
+ * would silently ignore.
32287
+ * @see https://commandcode.ai/docs/mcp
32288
+ */
32289
+ function convertToCommandcodeFormat(mcpServers, logger) {
32290
+ const result = {};
32291
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
32292
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
32293
+ if (declaresNoTransport(serverConfig)) {
32294
+ warnAndSkipMcpServer({
32295
+ toolName: "Command Code",
32296
+ serverName,
32297
+ reason: "no transport",
32298
+ logger
32299
+ });
32300
+ continue;
32301
+ }
32302
+ const converted = isRemoteMcpServer(serverConfig) ? convertRemoteServer$1({
32303
+ serverName,
32304
+ serverConfig,
32305
+ logger
32306
+ }) : convertStdioServer$1({
32307
+ serverName,
32308
+ serverConfig,
32309
+ logger
32310
+ });
32311
+ if (converted === void 0) continue;
32312
+ if (serverConfig.disabled === true) converted.enabled = false;
32313
+ if (isRecord$1(serverConfig.env)) converted.env = omitPrototypePollutionKeys(serverConfig.env);
32314
+ result[serverName] = converted;
32315
+ }
32316
+ return result;
32317
+ }
32318
+ function convertRemoteServer$1({ serverName, serverConfig, logger }) {
32319
+ const url = resolveRemoteMcpUrl(serverConfig);
32320
+ if (!url) {
32321
+ warnAndSkipMcpServer({
32322
+ toolName: "Command Code",
32323
+ serverName,
32324
+ reason: "a remote transport without a url",
32325
+ logger
32326
+ });
32327
+ return;
32328
+ }
32329
+ const stated = serverConfig.type ?? serverConfig.transport;
32330
+ const transport = asCommandcodeRemoteTransport(typeof stated === "string" ? stated : void 0, url);
32331
+ if (transport === void 0) {
32332
+ warnAndSkipMcpServer({
32333
+ toolName: "Command Code",
32334
+ serverName,
32335
+ reason: stated === void 0 ? "a WebSocket url, which Command Code's remote transports (http and sse) cannot reach" : `the "${String(stated)}" transport, which Command Code does not offer for remote servers (only http and sse)`,
32336
+ logger
32337
+ });
32338
+ return;
32339
+ }
32340
+ const converted = {
32341
+ transport,
32342
+ url
32343
+ };
32344
+ if (isRecord$1(serverConfig.headers)) converted.headers = omitPrototypePollutionKeys(serverConfig.headers);
32345
+ if (isRecord$1(serverConfig.oauth)) converted.oauth = omitPrototypePollutionKeys(serverConfig.oauth);
32346
+ return converted;
32347
+ }
32348
+ function convertStdioServer$1({ serverName, serverConfig, logger }) {
32349
+ const [command, ...args] = resolveLocalMcpCommand(serverConfig);
32350
+ if (!command) {
32351
+ warnAndSkipMcpServer({
32352
+ toolName: "Command Code",
32353
+ serverName,
32354
+ reason: "a stdio transport without a command",
32355
+ logger
32356
+ });
32357
+ return;
32358
+ }
32359
+ const converted = {
32360
+ transport: "stdio",
32361
+ command
32362
+ };
32363
+ if (args.length > 0) converted.args = args;
32364
+ return converted;
32365
+ }
32366
+ /**
32367
+ * Convert Command Code's server map back to the canonical shape. Both
32368
+ * spellings Command Code accepts (`transport` and its `type` alias, with
32369
+ * `command`/`url`) are already canonical, so entries pass through with only
32370
+ * prototype-pollution keys dropped; the native `enabled: false` maps to the
32371
+ * canonical `disabled: true` (a bare `enabled` would otherwise be read back
32372
+ * as rulesync's own generation filter and silently drop the server from every
32373
+ * other target).
32374
+ */
32375
+ function convertFromCommandcodeFormat(mcpServers) {
32376
+ if (!isMcpServers(mcpServers)) return {};
32377
+ const result = {};
32378
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
32379
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
32380
+ const { enabled, ...rest } = omitPrototypePollutionKeys(serverConfig);
32381
+ result[serverName] = enabled === false ? {
32382
+ ...rest,
32383
+ disabled: true
32384
+ } : rest;
32385
+ }
32386
+ return result;
32387
+ }
32388
+ /**
32389
+ * Command Code MCP configuration.
32390
+ *
32391
+ * Command Code reads `.mcp.json` at the project root (project scope, meant to
32392
+ * be committed) and `~/.commandcode/mcp.json` (user scope), both in the
32393
+ * `{ "mcpServers": { ... } }` shape the docs show; the per-machine local
32394
+ * scope under `~/.commandcode/projects/` is left to the tool. Top-level
32395
+ * sibling keys of an existing file are kept. The global file is not deleted
32396
+ * by `--delete` (it lives outside the project), while the project file is
32397
+ * rulesync's own and is.
32398
+ *
32399
+ * The project `.mcp.json` is the very file the `claudecode` target writes, so
32400
+ * at project scope the servers are written in the same pass-through shape
32401
+ * `ClaudecodeMcp` uses: whichever of the two targets generates last leaves
32402
+ * byte-identical content, and Command Code reads that shape natively (`type`
32403
+ * is an alias of `transport`, a bare `url` infers `http`, and unknown keys
32404
+ * are ignored). Only the global file — Command Code's own — gets the
32405
+ * `transport` / `enabled: false` rewrite of `convertToCommandcodeFormat`.
32406
+ *
32407
+ * @see https://commandcode.ai/docs/mcp
32408
+ */
32409
+ var CommandcodeMcp = class CommandcodeMcp extends ToolMcp {
32410
+ json;
32411
+ constructor(params) {
32412
+ super(params);
32413
+ this.json = this.fileContent === void 0 ? {} : parseCommandcodeMcpConfig({
32414
+ fileContent: this.fileContent,
32415
+ relativePath: join(this.relativeDirPath, this.relativeFilePath)
32416
+ });
32417
+ }
32418
+ getJson() {
32419
+ return this.json;
32420
+ }
32421
+ isDeletable() {
32422
+ return !this.global;
32423
+ }
32424
+ static getSettablePaths({ global = false } = {}) {
32425
+ return global ? {
32426
+ relativeDirPath: COMMANDCODE_DIR,
32427
+ relativeFilePath: COMMANDCODE_GLOBAL_MCP_FILE_NAME
32428
+ } : {
32429
+ relativeDirPath: ".",
32430
+ relativeFilePath: COMMANDCODE_PROJECT_MCP_FILE_NAME
32431
+ };
32432
+ }
32433
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
32434
+ const paths = this.getSettablePaths({ global });
32435
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
32436
+ return new CommandcodeMcp({
32437
+ outputRoot,
32438
+ relativeDirPath: paths.relativeDirPath,
32439
+ relativeFilePath: paths.relativeFilePath,
32440
+ fileContent,
32441
+ validate,
32442
+ global
32443
+ });
32444
+ }
32445
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
32446
+ const paths = this.getSettablePaths({ global });
32447
+ const json = parseCommandcodeMcpConfig({
32448
+ fileContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
32449
+ relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
32450
+ });
32451
+ const mcpServers = global ? convertToCommandcodeFormat(rulesyncMcp.getMcpServers(), logger) : rulesyncMcp.getMcpServers();
32452
+ const commandcodeConfig = {
32453
+ ...json,
32454
+ mcpServers
32455
+ };
32456
+ return new CommandcodeMcp({
32457
+ outputRoot,
32458
+ relativeDirPath: paths.relativeDirPath,
32459
+ relativeFilePath: paths.relativeFilePath,
32460
+ fileContent: JSON.stringify(commandcodeConfig, null, 2),
32461
+ validate,
32462
+ global
32463
+ });
32464
+ }
32465
+ toRulesyncMcp() {
32466
+ const mcpServers = convertFromCommandcodeFormat(this.json.mcpServers);
32467
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
32468
+ }
32469
+ validate() {
32470
+ return {
32471
+ success: true,
32472
+ error: null
32473
+ };
32474
+ }
32475
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
32476
+ return new CommandcodeMcp({
32477
+ outputRoot,
32478
+ relativeDirPath,
32479
+ relativeFilePath,
32480
+ fileContent: "{}",
32481
+ validate: false,
32482
+ global
32483
+ });
32484
+ }
32485
+ };
32486
+ //#endregion
31934
32487
  //#region src/features/mcp/continue-mcp.ts
31935
32488
  /**
31936
32489
  * Parse a Continue MCP file. Continue reads the files under `mcpServers/` as
@@ -36899,6 +37452,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
36899
37452
  static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
36900
37453
  const servers = rulesyncMcp.forTarget({
36901
37454
  toolTarget: "rovodev",
37455
+ global,
36902
37456
  logger
36903
37457
  }).getMcpServers();
36904
37458
  const managedNames = Object.keys(servers).filter((name) => toRovodevServer(name, servers[name]) !== null);
@@ -38196,6 +38750,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
38196
38750
  supportsDisabledTools: true
38197
38751
  }
38198
38752
  }],
38753
+ ["commandcode", {
38754
+ class: CommandcodeMcp,
38755
+ meta: {
38756
+ supportsProject: true,
38757
+ supportsGlobal: true,
38758
+ supportsEnabledTools: false,
38759
+ supportsDisabledTools: false
38760
+ }
38761
+ }],
38199
38762
  ["continue", {
38200
38763
  class: ContinueMcp,
38201
38764
  meta: {
@@ -38589,6 +39152,7 @@ var McpProcessor = class extends FeatureProcessor {
38589
39152
  const toolMcps = await Promise.all([rulesyncMcp].map(async (mcp) => {
38590
39153
  const targetedRulesyncMcp = mcp.forTarget({
38591
39154
  toolTarget: this.toolTarget,
39155
+ global: this.global,
38592
39156
  logger: this.logger
38593
39157
  });
38594
39158
  const fieldsToStrip = [];
@@ -42583,6 +43147,622 @@ function mapBashActionToDecision(action) {
42583
43147
  return "forbidden";
42584
43148
  }
42585
43149
  //#endregion
43150
+ //#region src/features/permissions/commandcode-permissions.ts
43151
+ /** Top-level key of `.commandcode/settings.json` that rulesync owns. */
43152
+ const COMMANDCODE_PERMISSIONS_KEY = "permissions";
43153
+ const CATCH_ALL_PATTERN$6 = "*";
43154
+ const MCP_CANONICAL_PREFIX$5 = "mcp__";
43155
+ const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
43156
+ const CATEGORY_TO_COMMANDCODE_TOOL = {
43157
+ bash: "Shell",
43158
+ read: "Read",
43159
+ edit: "Edit",
43160
+ write: "Write",
43161
+ grep: "Grep",
43162
+ glob: "Glob",
43163
+ webfetch: "WebFetch",
43164
+ websearch: "WebSearch"
43165
+ };
43166
+ const COMMANDCODE_TOOL_TO_CATEGORY = {
43167
+ ...Object.fromEntries(Object.entries(CATEGORY_TO_COMMANDCODE_TOOL).map(([category, tool]) => [tool.toLowerCase(), category])),
43168
+ bash: "bash",
43169
+ powershell: "bash",
43170
+ shell_command: "bash",
43171
+ monitor_command: "bash",
43172
+ kill_shell: "bash",
43173
+ notebookedit: "edit",
43174
+ write_file: "write",
43175
+ web_fetch: "webfetch",
43176
+ web_search: "websearch"
43177
+ };
43178
+ const ACTION_RANK$1 = {
43179
+ allow: 0,
43180
+ ask: 1,
43181
+ deny: 2
43182
+ };
43183
+ /** Whether `action` outranks `existing` (deny > ask > allow). */
43184
+ function isStricterAction({ action, existing }) {
43185
+ return existing === void 0 || ACTION_RANK$1[action] > ACTION_RANK$1[existing];
43186
+ }
43187
+ /**
43188
+ * Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
43189
+ * the whitespace makes it a pattern rule, and a shell pattern grants only
43190
+ * commands that pass Command Code's pattern gate.
43191
+ */
43192
+ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (the command must parse into words and carry no environment assignment)";
43193
+ /**
43194
+ * Build a Command Code rule (`Shell(git *)`, `Read`, `mcp__github__get_issue`,
43195
+ * `mcp__*`, `*`) from a canonical category + pattern. Returns `null` for
43196
+ * categories Command Code cannot express so the caller can skip them.
43197
+ *
43198
+ * MCP tools are keyed by their canonical `mcp__server__tool` name, so a
43199
+ * scoped `mcp__<remainder>` category is written verbatim and the bare `mcp`
43200
+ * category becomes `mcp__*` (or `mcp__<pattern>` when the pattern names a
43201
+ * server or tool). A pattern on an MCP category is still written as the
43202
+ * `(specifier)` here; `writableCommandcodeRule` decides what to do with it,
43203
+ * because Command Code ignores it. The all-tools `*` category is only
43204
+ * written for its catch-all pattern; narrower `*` patterns are shell
43205
+ * restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
43206
+ */
43207
+ function buildCommandcodeRule(category, pattern) {
43208
+ const catchAll = pattern === CATCH_ALL_PATTERN$6 || pattern === "";
43209
+ if (category.startsWith(MCP_CANONICAL_PREFIX$5)) return catchAll ? category : `${category}(${pattern})`;
43210
+ if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$5}${pattern}`;
43211
+ if (category === "*") return catchAll ? CATCH_ALL_PATTERN$6 : null;
43212
+ const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
43213
+ if (tool === void 0) return null;
43214
+ return catchAll ? tool : `${tool}(${pattern})`;
43215
+ }
43216
+ /**
43217
+ * Split `Tool(specifier)` into its two halves the way Command Code's
43218
+ * `splitRule` does (`command-code` 1.54.0): the rule is split at the first
43219
+ * unescaped `(` and must end with `)`, the tool half is trimmed (so
43220
+ * `Shell (rm -rf *)` is a `Shell` rule), and a `\(` / `\)` in the specifier
43221
+ * is an escaped parenthesis. `Tool` alone has an empty specifier. A rule that
43222
+ * opens a parenthesis without closing one is not a rule to Command Code at
43223
+ * all, so it comes back with an empty tool half and stays unmodeled. Nothing
43224
+ * else in the specifier is touched: Command Code does not trim it (the shell
43225
+ * matcher normalizes whitespace on its own, see `parseCommandcodeRule`), so
43226
+ * `Read( )` is a rule for the pattern `" "`, which matches nothing, not for
43227
+ * the whole tool.
43228
+ */
43229
+ function splitCommandcodeRule(rule) {
43230
+ const trimmed = rule.trim();
43231
+ const parenIndex = trimmed.search(/(?<!\\)\(/);
43232
+ if (parenIndex === -1) return {
43233
+ tool: trimmed,
43234
+ inner: ""
43235
+ };
43236
+ if (!trimmed.endsWith(")")) return {
43237
+ tool: "",
43238
+ inner: ""
43239
+ };
43240
+ return {
43241
+ tool: trimmed.slice(0, parenIndex).trim(),
43242
+ inner: trimmed.slice(parenIndex + 1, -1).replace(/\\([()])/g, "$1")
43243
+ };
43244
+ }
43245
+ /** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
43246
+ function isCatchAllSpecifier(inner) {
43247
+ return inner === "" || inner === CATCH_ALL_PATTERN$6;
43248
+ }
43249
+ /**
43250
+ * Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
43251
+ * `parsePermissionRule` / `parseMcpToken`). The MCP shape is only recognized
43252
+ * by its exact `mcp__` prefix, and such a rule never carries a specifier:
43253
+ * `mcp__<server>__<tool>(owner:foo)` denies, asks or allows the whole tool.
43254
+ * Its names are matched case-sensitively against the registered server and
43255
+ * tool (Command Code never lowercases them), so the remainder keeps its case
43256
+ * as the canonical category. `mcp__<server>`, `mcp__<server>__` and
43257
+ * `mcp__<server>__*` are the whole server (category `mcp__<server>`), and a
43258
+ * `*` server (`mcp__*`, `mcp__*__<tool>`) is every MCP tool, honored in
43259
+ * `deny`/`ask` only. A differently-cased `MCP__...` spelling is read as a
43260
+ * plain tool-name rule instead, matched case-insensitively:
43261
+ * `MCP__<server>__<tool>` matches that tool by name (so it folds onto the
43262
+ * `mcp__` category, remainder as written), `MCP__<server>__<tool>(specifier)`
43263
+ * globs the specifier against the call's `command` / `file_path` / `path` /
43264
+ * `url` / `pattern` argument (in `deny`/`ask` a `param:glob` form is tried
43265
+ * first) — kept as a pattern in `deny`/`ask`, but not imported from `allow`,
43266
+ * where a grant scoped that way cannot be modeled without widening it; the
43267
+ * globs
43268
+ * `MCP__*` (every MCP tool) and `MCP__<server>__*` (the whole server) match
43269
+ * by name in `deny`/`ask` only, and `MCP__<server>` names no tool at all.
43270
+ * Whatever matches nothing anywhere (or a glob rulesync does not model)
43271
+ * returns `null` and is left alone.
43272
+ */
43273
+ function parseMcpCommandcodeRule({ tool, inner }) {
43274
+ const remainder = tool.slice(5);
43275
+ const separator = remainder.indexOf("__");
43276
+ const server = separator === -1 ? remainder : remainder.slice(0, separator);
43277
+ const rest = separator === -1 ? "" : remainder.slice(separator + 2);
43278
+ if (server.length === 0) return null;
43279
+ const everyServer = {
43280
+ category: "mcp",
43281
+ pattern: CATCH_ALL_PATTERN$6,
43282
+ notInAllow: true
43283
+ };
43284
+ if (tool.startsWith(MCP_CANONICAL_PREFIX$5)) {
43285
+ if (server === CATCH_ALL_PATTERN$6) return everyServer;
43286
+ if (server.includes(CATCH_ALL_PATTERN$6)) return null;
43287
+ return {
43288
+ category: rest === "" || rest === CATCH_ALL_PATTERN$6 ? `${MCP_CANONICAL_PREFIX$5}${server}` : `${MCP_CANONICAL_PREFIX$5}${remainder}`,
43289
+ pattern: CATCH_ALL_PATTERN$6,
43290
+ notInAllow: false
43291
+ };
43292
+ }
43293
+ const category = `${MCP_CANONICAL_PREFIX$5}${remainder}`;
43294
+ const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$6);
43295
+ if (!isCatchAllSpecifier(inner)) return namesTool ? {
43296
+ category,
43297
+ pattern: inner,
43298
+ notInAllow: true
43299
+ } : null;
43300
+ if (remainder === CATCH_ALL_PATTERN$6) return everyServer;
43301
+ if (rest === CATCH_ALL_PATTERN$6 && !server.includes(CATCH_ALL_PATTERN$6)) return {
43302
+ category: `${MCP_CANONICAL_PREFIX$5}${server}`,
43303
+ pattern: CATCH_ALL_PATTERN$6,
43304
+ notInAllow: true
43305
+ };
43306
+ return namesTool ? {
43307
+ category,
43308
+ pattern: CATCH_ALL_PATTERN$6,
43309
+ notInAllow: false
43310
+ } : null;
43311
+ }
43312
+ /**
43313
+ * A rule on one of the friendly tools, with its specifier read as Command
43314
+ * Code matches it. The shell matcher trims and collapses whitespace on both
43315
+ * the pattern and the command, so `Shell( git * )` is `Shell(git *)` and a
43316
+ * pattern that is blank once normalized (`Shell( )`), or a bare `:*` whose
43317
+ * prefix is empty (`Shell(:*)`), matches nothing. A padded `Shell( * )` is
43318
+ * not the bare `Shell`: only a specifier of exactly
43319
+ * `` or `*` is dropped by the rule parser, so `( * )` stays a pattern rule —
43320
+ * in `deny`/`ask` it matches every command (the whole tool), while in `allow`
43321
+ * it is narrower than the bare `Shell` (it needs a command that parses into
43322
+ * words and has no environment-assignment prefix), so it is the whole tool
43323
+ * marked `notInAllow`. The path, web and tool-name matchers use the
43324
+ * specifier as written, so one that is blank or `*` only once trimmed
43325
+ * (`Read( * )`) matches nothing. What matches nothing comes back as `null`:
43326
+ * the rule is unmodeled and left alone.
43327
+ */
43328
+ function friendlyToolRule({ category, inner }) {
43329
+ if (category === "bash") {
43330
+ if (isCatchAllSpecifier(inner)) return {
43331
+ category,
43332
+ pattern: CATCH_ALL_PATTERN$6,
43333
+ notInAllow: false
43334
+ };
43335
+ const normalized = inner.trim().replace(/\s+/g, " ");
43336
+ if (normalized === "" || normalized === ":*") return null;
43337
+ return isCatchAllSpecifier(normalized) ? {
43338
+ category,
43339
+ pattern: CATCH_ALL_PATTERN$6,
43340
+ notInAllow: true
43341
+ } : {
43342
+ category,
43343
+ pattern: normalized,
43344
+ notInAllow: false
43345
+ };
43346
+ }
43347
+ if (isCatchAllSpecifier(inner)) return {
43348
+ category,
43349
+ pattern: CATCH_ALL_PATTERN$6,
43350
+ notInAllow: false
43351
+ };
43352
+ return isCatchAllSpecifier(inner.trim()) ? null : {
43353
+ category,
43354
+ pattern: inner,
43355
+ notInAllow: false
43356
+ };
43357
+ }
43358
+ /**
43359
+ * Parse a Command Code rule back into canonical terms. Tool names fold case;
43360
+ * `Tool`, `Tool()` and `Tool(*)` all mean the whole tool; MCP-shaped rules
43361
+ * go through `parseMcpCommandcodeRule`. Returns `null` for a rule rulesync
43362
+ * cannot model (an exact internal tool name such as `edit_file`, a
43363
+ * name-wildcard like `edit_*`, a specifier on a rule that takes none, or a
43364
+ * rule Command Code enforces nothing for, such as `Agent`).
43365
+ */
43366
+ function parseCommandcodeRule(rule) {
43367
+ const { tool, inner } = splitCommandcodeRule(rule);
43368
+ if (tool === CATCH_ALL_PATTERN$6) return isCatchAllSpecifier(inner) ? {
43369
+ category: "*",
43370
+ pattern: CATCH_ALL_PATTERN$6,
43371
+ notInAllow: true
43372
+ } : null;
43373
+ const lowered = tool.toLowerCase();
43374
+ if (lowered.startsWith(MCP_CANONICAL_PREFIX$5)) return parseMcpCommandcodeRule({
43375
+ tool,
43376
+ inner
43377
+ });
43378
+ const category = Object.hasOwn(COMMANDCODE_TOOL_TO_CATEGORY, lowered) ? COMMANDCODE_TOOL_TO_CATEGORY[lowered] : void 0;
43379
+ if (category === void 0) return null;
43380
+ return friendlyToolRule({
43381
+ category,
43382
+ inner
43383
+ });
43384
+ }
43385
+ /**
43386
+ * Whether an imported rule is a differently-cased `MCP__<server>__<tool>`
43387
+ * name that folded onto an exact-prefix `mcp__` category (which Command Code
43388
+ * matches case-sensitively, unlike the name rule it came from).
43389
+ */
43390
+ function isFoldedMcpToolNameRule({ rule, category }) {
43391
+ const { tool } = splitCommandcodeRule(rule);
43392
+ return category.startsWith(MCP_CANONICAL_PREFIX$5) && !tool.startsWith(MCP_CANONICAL_PREFIX$5);
43393
+ }
43394
+ /** The `Tool` half of `Tool(specifier)`, when the rule is an exact-prefix MCP rule with a specifier. */
43395
+ function scopedMcpRuleTool(rule) {
43396
+ const { tool, inner } = splitCommandcodeRule(rule);
43397
+ return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$5) ? tool : null;
43398
+ }
43399
+ /**
43400
+ * The string entries of a Command Code permission list. Command Code reads
43401
+ * each entry on its own and skips one that is not a string, so a stray
43402
+ * `null` beside `Shell(rm -rf *)` must not throw the deny away with it; the
43403
+ * skipped entries are counted in a warning because they cannot be kept.
43404
+ */
43405
+ function stringRules({ list, action, outcome, warn }) {
43406
+ if (!Array.isArray(list)) return [];
43407
+ const rules = list.filter((entry) => typeof entry === "string");
43408
+ const skipped = list.length - rules.length;
43409
+ if (skipped > 0) warn(`Command Code permission list "${action}" holds ${skipped} ${skipped === 1 ? "entry" : "entries"} that ${skipped === 1 ? "is" : "are"} not a string, which Command Code skips; ${skipped === 1 ? "that entry was" : "those entries were"} ${outcome}.`);
43410
+ return rules;
43411
+ }
43412
+ /**
43413
+ * The canonical categories this run rebuilds — every category the canonical
43414
+ * config names (an empty `bash: {}` still reclaims the previous `Shell(...)`
43415
+ * entries, as in the Claude Code adapter) plus every rule it emits, keyed the
43416
+ * way an existing entry parses back (so `mcp: { github: "deny" }`, written as
43417
+ * `mcp__github`, claims the `mcp__github` entries of the previous run; case
43418
+ * variants of the friendly tool names fold together, while MCP names are
43419
+ * matched exactly, as Command Code does). An existing entry whose tool folds onto one of them is
43420
+ * rulesync's to replace, whatever list it sits in — otherwise flipping a rule
43421
+ * from deny to allow would leave the old deny behind and win. Every other
43422
+ * entry — an internal tool name rulesync cannot model, or a modeled tool the
43423
+ * canonical config does not mention — is the user's (Command Code writes
43424
+ * interactive approvals into the same lists) and is preserved verbatim.
43425
+ * A `deny`/`ask` entry that is reclaimed without an equally strict rule
43426
+ * taking its place (say a hand-written `mcp__*` deny next to a canonical
43427
+ * `mcp: { github: "deny" }`) is reported, so regenerating never silently
43428
+ * loosens what the user wrote. Mirrors `managedClaudeToolNames` in the
43429
+ * Claude Code adapter.
43430
+ */
43431
+ function preservedRules({ existingPermissions, key, managedCategories, written, logger }) {
43432
+ return stringRules({
43433
+ list: existingPermissions[key],
43434
+ action: key,
43435
+ outcome: "dropped from the regenerated list",
43436
+ warn: (message) => logger?.warn(message)
43437
+ }).filter((rule) => {
43438
+ const parsed = parseCommandcodeRule(rule);
43439
+ if (parsed === null || !managedCategories.has(parsed.category)) return true;
43440
+ const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$6})`);
43441
+ if (key !== "allow" && isStricterAction({
43442
+ action: key,
43443
+ existing: replacement
43444
+ })) logger?.warn(`Command Code permission rule '${rule}' in "${key}" belongs to the '${parsed.category}' category, which the rulesync config now manages, and was replaced by its rules.`);
43445
+ return false;
43446
+ });
43447
+ }
43448
+ /**
43449
+ * The rule to write for a canonical rule, or `null` when Command Code would
43450
+ * ignore it in the `action` list. A server-less wildcard (`*`, `mcp__*`) in
43451
+ * `allow` is warned about and left unwritten rather than written dead. A
43452
+ * specifier on an MCP rule is ignored by Command Code in every list, so a
43453
+ * scoped `deny`/`ask` is written as the bare tool (which is what Command
43454
+ * Code would enforce anyway, only now visibly) with a warning, and a scoped
43455
+ * `allow` — which would grant the whole tool — is refused. Judged on the
43456
+ * rule as written, so the bare `mcp` category cannot smuggle a specifier in
43457
+ * through a `<server>__<tool>(specifier)` pattern.
43458
+ */
43459
+ function writableCommandcodeRule({ rule, emitted, action, logger }) {
43460
+ if (action === "allow" && emitted.notInAllow) {
43461
+ logger?.warn(emitted.category === "bash" ? `Command Code reads '${rule}' in "allow" as ${PADDED_SHELL_WILDCARD_REASON}, so the '${emitted.category}' allow rule was not written; use the '*' pattern for the whole tool.` : `Command Code does not honor '${rule}' in "allow" as written (an allow rule must name what it grants), so the '${emitted.category}' allow rule was not written.`);
43462
+ return null;
43463
+ }
43464
+ const tool = scopedMcpRuleTool(rule);
43465
+ if (tool === null) return rule;
43466
+ if (action === "allow") {
43467
+ logger?.warn(`Command Code ignores the specifier of an MCP rule and would allow the whole '${tool}' tool, so the '${rule}' allow rule was not written.`);
43468
+ return null;
43469
+ }
43470
+ logger?.warn(`Command Code ignores the specifier of an MCP rule, so '${rule}' was written as '${tool}' and applies "${action}" to the whole tool.`);
43471
+ return tool;
43472
+ }
43473
+ /**
43474
+ * Record `action` for `rule`, keeping the stricter one (deny > ask > allow)
43475
+ * when two canonical rules collapse onto the same Command Code entry.
43476
+ */
43477
+ function rankCommandcodeRule({ ranked, rule, action, logger }) {
43478
+ const existing = ranked.get(rule);
43479
+ if (existing !== void 0 && existing !== action) logger?.warn(`Command Code permission rule '${rule}' received conflicting actions ('${existing}' and '${action}'); keeping the stricter one (deny > ask > allow).`);
43480
+ if (isStricterAction({
43481
+ action,
43482
+ existing
43483
+ })) ranked.set(rule, action);
43484
+ }
43485
+ /**
43486
+ * Bucket the canonical rules into Command Code's `allow`/`ask`/`deny` lists.
43487
+ * Collisions resolve to the strictest action (deny > ask > allow); categories
43488
+ * Command Code cannot express are skipped with a warning when they carry a
43489
+ * `deny`; rules Command Code would ignore in the target list are dropped or
43490
+ * rewritten by `writableCommandcodeRule`.
43491
+ */
43492
+ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
43493
+ const permission = honorAllToolsOnBash(config.permission);
43494
+ const ranked = /* @__PURE__ */ new Map();
43495
+ const managedCategories = /* @__PURE__ */ new Set();
43496
+ for (const [category, rules] of Object.entries(permission)) {
43497
+ const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$6);
43498
+ const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
43499
+ if (managedCategory !== null) managedCategories.add(managedCategory.category);
43500
+ for (const [pattern, action] of Object.entries(rules)) {
43501
+ const rule = buildCommandcodeRule(category, pattern);
43502
+ const emitted = rule === null ? null : parseCommandcodeRule(rule);
43503
+ if (rule === null || emitted === null) {
43504
+ const honoredOnShell = category === "*" && permission.bash !== void 0;
43505
+ if (action === "deny" && !honoredOnShell) logger?.warn(`Command Code has no permission rule for the '${category}' category with pattern '${pattern}'; its 'deny' rule could not be represented and was skipped.`);
43506
+ continue;
43507
+ }
43508
+ managedCategories.add(emitted.category);
43509
+ const written = writableCommandcodeRule({
43510
+ rule,
43511
+ emitted,
43512
+ action,
43513
+ logger
43514
+ });
43515
+ if (written !== null) rankCommandcodeRule({
43516
+ ranked,
43517
+ rule: written,
43518
+ action,
43519
+ logger
43520
+ });
43521
+ }
43522
+ }
43523
+ const written = /* @__PURE__ */ new Map();
43524
+ for (const [rule, action] of ranked) {
43525
+ const parsed = parseCommandcodeRule(rule);
43526
+ if (parsed !== null) written.set(`${parsed.category}(${parsed.pattern})`, action);
43527
+ }
43528
+ const preserved = {
43529
+ existingPermissions,
43530
+ managedCategories,
43531
+ written,
43532
+ logger
43533
+ };
43534
+ const allow = preservedRules({
43535
+ ...preserved,
43536
+ key: "allow"
43537
+ });
43538
+ const ask = preservedRules({
43539
+ ...preserved,
43540
+ key: "ask"
43541
+ });
43542
+ const deny = preservedRules({
43543
+ ...preserved,
43544
+ key: "deny"
43545
+ });
43546
+ for (const [rule, action] of ranked) if (action === "allow") allow.push(rule);
43547
+ else if (action === "ask") ask.push(rule);
43548
+ else deny.push(rule);
43549
+ return {
43550
+ allow: uniq(allow.toSorted()),
43551
+ ask: uniq(ask.toSorted()),
43552
+ deny: uniq(deny.toSorted())
43553
+ };
43554
+ }
43555
+ /**
43556
+ * Why a rule marked `notInAllow` is not imported from `allow`: Command Code
43557
+ * ignores a server-less wildcard or a tool-name glob there, while it does
43558
+ * enforce a scoped `MCP__<server>__<tool>(specifier)` — as a glob over the
43559
+ * call's arguments — and a padded `Shell( * )` — as a pattern narrower than
43560
+ * the bare `Shell` — which rulesync cannot model in `allow` without widening
43561
+ * the grant.
43562
+ */
43563
+ function skippedAllowImportMessage({ rule, category }) {
43564
+ const { tool, inner } = splitCommandcodeRule(rule);
43565
+ if (category === "bash") return `Command Code reads '${rule}' in "allow" as ${PADDED_SHELL_WILDCARD_REASON}, which rulesync cannot import without widening the grant to every command, so it was not imported.`;
43566
+ if (tool.startsWith(MCP_CANONICAL_PREFIX$5) || isCatchAllSpecifier(inner)) return `Command Code ignores '${rule}' in "allow" (an allow rule must name what it grants), so it was not imported.`;
43567
+ return `Command Code globs the specifier of '${rule}' in "allow" against the call's arguments, which rulesync cannot import without widening the grant to the whole tool, so it was not imported.`;
43568
+ }
43569
+ /**
43570
+ * Parse Command Code's `permissions` lists back into a canonical permission
43571
+ * map with `deny > ask > allow` precedence, so a tool described more than
43572
+ * once resolves to the strictest action. Each rule is imported as what
43573
+ * Command Code enforces for it, never as what it looks like: a rule Command
43574
+ * Code ignores in `allow` (`*`, `mcp__*`, an `MCP__...` glob) is skipped
43575
+ * there with a warning, since importing it would turn a dead line into a
43576
+ * live grant for every other target, and so is a scoped
43577
+ * `MCP__<server>__<tool>(specifier)` allow, which Command Code enforces as a
43578
+ * glob over the call's arguments but rulesync cannot import without widening
43579
+ * it to the whole tool; the specifier of an exact-prefix `mcp__...` rule,
43580
+ * which Command Code drops, is folded to the whole tool in every list — with
43581
+ * a warning in `allow`, where the fold widens the grant the user wrote. A
43582
+ * tool-half glob in that exact-prefix spelling (`mcp__<server>__get_*`), which
43583
+ * Command Code matches against tool names in every list, is imported as the
43584
+ * category spelled that way, the same one the generator writes back; other
43585
+ * targets read it as a literal tool name.
43586
+ */
43587
+ function parseCommandcodeRuleLists(permissions) {
43588
+ const permission = {};
43589
+ const lists = [
43590
+ ["allow", permissions.allow],
43591
+ ["ask", permissions.ask],
43592
+ ["deny", permissions.deny]
43593
+ ];
43594
+ for (const [action, rawList] of lists) {
43595
+ const list = stringRules({
43596
+ list: rawList,
43597
+ action,
43598
+ outcome: "not imported",
43599
+ warn: (message) => fallbackLogger.warn(message)
43600
+ });
43601
+ for (const rule of list) {
43602
+ const parsed = parseCommandcodeRule(rule);
43603
+ if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) {
43604
+ if (action !== "allow") fallbackLogger.warn(`Command Code permission rule '${rule}' in "${action}" is not one rulesync can model, so it could not be imported; it stays in the Command Code settings, where a regenerate keeps it unless the canonical config manages its category.`);
43605
+ continue;
43606
+ }
43607
+ const { category, pattern } = parsed;
43608
+ if (action === "allow" && parsed.notInAllow) {
43609
+ fallbackLogger.warn(skippedAllowImportMessage({
43610
+ rule,
43611
+ category
43612
+ }));
43613
+ continue;
43614
+ }
43615
+ if (action === "allow" && scopedMcpRuleTool(rule) !== null) fallbackLogger.warn(`Command Code ignores the specifier of an MCP rule, so '${rule}' in "allow" was imported as the whole '${category}' tool, which is what Command Code grants for it.`);
43616
+ if (isFoldedMcpToolNameRule({
43617
+ rule,
43618
+ category
43619
+ })) fallbackLogger.warn(`Command Code matches '${rule}' in "${action}" against MCP tool names case-insensitively, but the '${category}' rule generated from it matches the server and tool names exactly as written; spell the rule the way the tool is registered if they differ.`);
43620
+ const rules = permission[category] ??= {};
43621
+ if (isStricterAction({
43622
+ action,
43623
+ existing: rules[pattern]
43624
+ })) rules[pattern] = action;
43625
+ }
43626
+ }
43627
+ return permission;
43628
+ }
43629
+ /**
43630
+ * Permissions generator for Command Code.
43631
+ *
43632
+ * Rules live under the `permissions` key of `.commandcode/settings.json`
43633
+ * (project) and `~/.commandcode/settings.json` (user) as three lists of
43634
+ * Claude-style entries: `deny` (always wins), `ask`, `allow`. The same key
43635
+ * also carries `defaultMode`, `additionalDirectories` and `disableBypass`,
43636
+ * and the file holds `hooks` and other settings, so writes go through the
43637
+ * shared-config gateway: rulesync replaces the three lists, keeps every
43638
+ * sibling key, and never deletes the file.
43639
+ *
43640
+ * Generate: `permission.<category>.<pattern>` becomes `Tool(pattern)` with
43641
+ * the friendly names (`Shell`, `Read`, `Edit`, `Write`, `WebFetch`,
43642
+ * `WebSearch`), `mcp__<server>__<tool>` names pass through, and the all-tools
43643
+ * `*` category becomes the bare `*` rule Command Code accepts in `deny`/`ask`.
43644
+ * Only the entries of the categories the canonical config names are rebuilt;
43645
+ * every other existing entry is preserved verbatim.
43646
+ * Import: the lists are parsed back as what Command Code enforces for each
43647
+ * entry — friendly tool names case-insensitively (with the internal aliases
43648
+ * such as `Bash`, `PowerShell` and `write_file` folding onto their
43649
+ * category), MCP names exactly; rules for internal tool names rulesync does
43650
+ * not model are skipped.
43651
+ *
43652
+ * @see https://commandcode.ai/docs/permissions
43653
+ * @see https://commandcode.ai/docs/settings
43654
+ */
43655
+ var CommandcodePermissions = class CommandcodePermissions extends ToolPermissions {
43656
+ constructor(params) {
43657
+ super({
43658
+ ...params,
43659
+ fileContent: params.fileContent ?? "{}"
43660
+ });
43661
+ }
43662
+ /**
43663
+ * `settings.json` holds hooks and other user settings, so it is never
43664
+ * deleted; clearing permissions happens via an in-place merge.
43665
+ */
43666
+ isDeletable() {
43667
+ return false;
43668
+ }
43669
+ static getSettablePaths(_options = {}) {
43670
+ return {
43671
+ relativeDirPath: COMMANDCODE_DIR,
43672
+ relativeFilePath: COMMANDCODE_SETTINGS_FILE_NAME
43673
+ };
43674
+ }
43675
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
43676
+ const paths = CommandcodePermissions.getSettablePaths({ global });
43677
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
43678
+ return new CommandcodePermissions({
43679
+ outputRoot,
43680
+ relativeDirPath: paths.relativeDirPath,
43681
+ relativeFilePath: paths.relativeFilePath,
43682
+ fileContent,
43683
+ validate,
43684
+ global
43685
+ });
43686
+ }
43687
+ static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
43688
+ const paths = CommandcodePermissions.getSettablePaths({ global });
43689
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
43690
+ const existingContent = await readFileContentOrNull(filePath) ?? "{}";
43691
+ const existing = parseCommandcodeSettings({
43692
+ fileContent: existingContent,
43693
+ relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
43694
+ });
43695
+ const existingPermissions = isRecord$1(existing[COMMANDCODE_PERMISSIONS_KEY]) ? existing[COMMANDCODE_PERMISSIONS_KEY] : {};
43696
+ const lists = buildCommandcodeRuleLists({
43697
+ config: rulesyncPermissions.getJson(),
43698
+ existingPermissions,
43699
+ logger
43700
+ });
43701
+ const permissions = { ...existingPermissions };
43702
+ for (const key of [
43703
+ "allow",
43704
+ "ask",
43705
+ "deny"
43706
+ ]) if (lists[key].length > 0) permissions[key] = lists[key];
43707
+ else delete permissions[key];
43708
+ return new CommandcodePermissions({
43709
+ outputRoot,
43710
+ relativeDirPath: paths.relativeDirPath,
43711
+ relativeFilePath: paths.relativeFilePath,
43712
+ fileContent: applySharedConfigPatch({
43713
+ fileKey: sharedConfigFileKey(paths),
43714
+ feature: "permissions",
43715
+ existingContent,
43716
+ patch: { [COMMANDCODE_PERMISSIONS_KEY]: permissions },
43717
+ filePath
43718
+ }),
43719
+ validate: true,
43720
+ global
43721
+ });
43722
+ }
43723
+ toRulesyncPermissions() {
43724
+ const settings = parseCommandcodeSettings({
43725
+ fileContent: this.getFileContent() || "{}",
43726
+ relativePath: join(this.getRelativeDirPath(), this.getRelativeFilePath())
43727
+ });
43728
+ const permissions = isRecord$1(settings[COMMANDCODE_PERMISSIONS_KEY]) ? settings[COMMANDCODE_PERMISSIONS_KEY] : {};
43729
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission: parseCommandcodeRuleLists(permissions) }, null, 2) });
43730
+ }
43731
+ validate() {
43732
+ return {
43733
+ success: true,
43734
+ error: null
43735
+ };
43736
+ }
43737
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
43738
+ return new CommandcodePermissions({
43739
+ outputRoot,
43740
+ relativeDirPath,
43741
+ relativeFilePath,
43742
+ fileContent: "{}",
43743
+ validate: false,
43744
+ global
43745
+ });
43746
+ }
43747
+ };
43748
+ /**
43749
+ * Fail closed on a syntax error or non-object root, matching the write path's
43750
+ * shared-config declaration, so a broken file is surfaced rather than
43751
+ * partially imported or overwritten.
43752
+ */
43753
+ function parseCommandcodeSettings({ fileContent, relativePath }) {
43754
+ try {
43755
+ return parseSharedConfig({
43756
+ format: "json",
43757
+ fileContent,
43758
+ filePath: relativePath,
43759
+ invalidRootPolicy: "error"
43760
+ });
43761
+ } catch (error) {
43762
+ throw new Error(`Failed to parse Command Code settings in ${relativePath}: ${formatError(error)}`, { cause: error });
43763
+ }
43764
+ }
43765
+ //#endregion
42586
43766
  //#region src/features/permissions/continue-permissions.ts
42587
43767
  const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
42588
43768
  const CATCH_ALL_PATTERN$5 = "*";
@@ -53344,6 +54524,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
53344
54524
  supportsImport: true
53345
54525
  }
53346
54526
  }],
54527
+ ["commandcode", {
54528
+ class: CommandcodePermissions,
54529
+ meta: {
54530
+ supportsProject: true,
54531
+ supportsGlobal: true,
54532
+ supportsImport: true
54533
+ }
54534
+ }],
53347
54535
  ["continue", {
53348
54536
  class: ContinuePermissions,
53349
54537
  meta: {
@@ -56110,6 +57298,154 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
56110
57298
  }
56111
57299
  };
56112
57300
  //#endregion
57301
+ //#region src/features/skills/commandcode-skill.ts
57302
+ const CommandcodeSkillFrontmatterSchema = z.looseObject({
57303
+ name: z.string(),
57304
+ description: z.string()
57305
+ });
57306
+ /**
57307
+ * Represents a Command Code skill directory.
57308
+ *
57309
+ * Command Code discovers Anthropic-style `<name>/SKILL.md` directories under
57310
+ * `<project>/.commandcode/skills/` (project scope) and
57311
+ * `~/.commandcode/skills/` (user scope). The frontmatter requires `name`
57312
+ * (matching the directory name) and `description`; supporting files next to
57313
+ * `SKILL.md` are carried along.
57314
+ *
57315
+ * @see https://commandcode.ai/docs/skills
57316
+ */
57317
+ var CommandcodeSkill = class CommandcodeSkill extends ToolSkill {
57318
+ constructor({ outputRoot = process.cwd(), relativeDirPath = COMMANDCODE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
57319
+ super({
57320
+ outputRoot,
57321
+ relativeDirPath,
57322
+ dirName,
57323
+ mainFile: {
57324
+ name: SKILL_FILE_NAME,
57325
+ body,
57326
+ frontmatter: { ...frontmatter }
57327
+ },
57328
+ otherFiles,
57329
+ global
57330
+ });
57331
+ if (validate) {
57332
+ const result = this.validate();
57333
+ if (!result.success) throw result.error;
57334
+ }
57335
+ }
57336
+ static getSettablePaths(_options = {}) {
57337
+ return { relativeDirPath: COMMANDCODE_SKILLS_DIR_PATH };
57338
+ }
57339
+ getFrontmatter() {
57340
+ return CommandcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
57341
+ }
57342
+ getBody() {
57343
+ return this.mainFile?.body ?? "";
57344
+ }
57345
+ validate() {
57346
+ if (!this.mainFile) return {
57347
+ success: false,
57348
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
57349
+ };
57350
+ const result = CommandcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
57351
+ if (!result.success) return {
57352
+ success: false,
57353
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
57354
+ };
57355
+ if (result.data.name !== this.getDirName()) return {
57356
+ success: false,
57357
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
57358
+ };
57359
+ return {
57360
+ success: true,
57361
+ error: null
57362
+ };
57363
+ }
57364
+ toRulesyncSkill() {
57365
+ const { name, description, ...commandcodeSection } = this.getFrontmatter();
57366
+ const rulesyncFrontmatter = {
57367
+ name,
57368
+ description,
57369
+ targets: ["*"],
57370
+ ...Object.keys(commandcodeSection).length > 0 && { commandcode: commandcodeSection }
57371
+ };
57372
+ return new RulesyncSkill({
57373
+ outputRoot: this.outputRoot,
57374
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
57375
+ dirName: this.getDirName(),
57376
+ frontmatter: rulesyncFrontmatter,
57377
+ body: this.getBody(),
57378
+ otherFiles: this.getOtherFiles(),
57379
+ validate: true,
57380
+ global: this.global
57381
+ });
57382
+ }
57383
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
57384
+ const settablePaths = CommandcodeSkill.getSettablePaths({ global });
57385
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
57386
+ const { name: _sectionName, description: _sectionDescription, ...commandcodeSection } = rulesyncFrontmatter.commandcode ?? {};
57387
+ const commandcodeFrontmatter = {
57388
+ ...commandcodeSection,
57389
+ name: rulesyncFrontmatter.name,
57390
+ description: rulesyncFrontmatter.description
57391
+ };
57392
+ return new CommandcodeSkill({
57393
+ outputRoot,
57394
+ relativeDirPath: settablePaths.relativeDirPath,
57395
+ dirName: commandcodeFrontmatter.name,
57396
+ frontmatter: commandcodeFrontmatter,
57397
+ body: rulesyncSkill.getBody(),
57398
+ otherFiles: rulesyncSkill.getOtherFiles(),
57399
+ validate,
57400
+ global
57401
+ });
57402
+ }
57403
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
57404
+ const targets = rulesyncSkill.getFrontmatter().targets;
57405
+ return targets.includes("*") || targets.includes("commandcode");
57406
+ }
57407
+ static async fromDir(params) {
57408
+ const loaded = await this.loadSkillDirContent({
57409
+ ...params,
57410
+ getSettablePaths: CommandcodeSkill.getSettablePaths
57411
+ });
57412
+ const result = CommandcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
57413
+ if (!result.success) {
57414
+ const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
57415
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
57416
+ }
57417
+ if (result.data.name !== loaded.dirName) {
57418
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
57419
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
57420
+ }
57421
+ return new CommandcodeSkill({
57422
+ outputRoot: loaded.outputRoot,
57423
+ relativeDirPath: loaded.relativeDirPath,
57424
+ dirName: loaded.dirName,
57425
+ frontmatter: result.data,
57426
+ body: loaded.body,
57427
+ otherFiles: loaded.otherFiles,
57428
+ validate: true,
57429
+ global: loaded.global
57430
+ });
57431
+ }
57432
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
57433
+ return new CommandcodeSkill({
57434
+ outputRoot,
57435
+ relativeDirPath,
57436
+ dirName,
57437
+ frontmatter: {
57438
+ name: "",
57439
+ description: ""
57440
+ },
57441
+ body: "",
57442
+ otherFiles: [],
57443
+ validate: false,
57444
+ global
57445
+ });
57446
+ }
57447
+ };
57448
+ //#endregion
56113
57449
  //#region src/features/skills/continue-skill.ts
56114
57450
  const ContinueSkillFrontmatterSchema = z.looseObject({
56115
57451
  name: z.string(),
@@ -61299,6 +62635,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
61299
62635
  supportsGlobal: true
61300
62636
  }
61301
62637
  }],
62638
+ ["commandcode", {
62639
+ class: CommandcodeSkill,
62640
+ meta: {
62641
+ supportsProject: true,
62642
+ supportsSimulated: false,
62643
+ supportsGlobal: true
62644
+ }
62645
+ }],
61302
62646
  ["continue", {
61303
62647
  class: ContinueSkill,
61304
62648
  meta: {
@@ -63402,6 +64746,143 @@ var CodexCliSubagent = class CodexCliSubagent extends ToolSubagent {
63402
64746
  }
63403
64747
  };
63404
64748
  //#endregion
64749
+ //#region src/features/subagents/commandcode-subagent.ts
64750
+ const CommandcodeSubagentFrontmatterSchema = z.looseObject({
64751
+ name: z.string(),
64752
+ description: z.optional(z.string()),
64753
+ tools: z.optional(z.union([z.string(), z.array(z.string())])),
64754
+ disallowedTools: z.optional(z.union([z.string(), z.array(z.string())])),
64755
+ model: z.optional(z.string()),
64756
+ reasoningEffort: z.optional(z.string()),
64757
+ maxTurns: z.optional(z.number()),
64758
+ permissionMode: z.optional(z.string()),
64759
+ background: z.optional(z.boolean()),
64760
+ showOutput: z.optional(z.boolean())
64761
+ });
64762
+ /**
64763
+ * Command Code custom agent: a Markdown file with YAML frontmatter under
64764
+ * `.commandcode/agents/` (project) or `~/.commandcode/agents/` (user); the
64765
+ * body is the agent's system prompt.
64766
+ *
64767
+ * @see https://commandcode.ai/docs/custom-agents
64768
+ */
64769
+ var CommandcodeSubagent = class CommandcodeSubagent extends ToolSubagent {
64770
+ frontmatter;
64771
+ body;
64772
+ constructor({ frontmatter, body, fileContent, ...rest }) {
64773
+ if (rest.validate !== false) {
64774
+ const result = CommandcodeSubagentFrontmatterSchema.safeParse(frontmatter);
64775
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
64776
+ }
64777
+ super({
64778
+ ...rest,
64779
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
64780
+ });
64781
+ this.frontmatter = frontmatter;
64782
+ this.body = body;
64783
+ }
64784
+ static getSettablePaths(_options = {}) {
64785
+ return { relativeDirPath: COMMANDCODE_AGENTS_DIR_PATH };
64786
+ }
64787
+ getFrontmatter() {
64788
+ return this.frontmatter;
64789
+ }
64790
+ getBody() {
64791
+ return this.body;
64792
+ }
64793
+ toRulesyncSubagent() {
64794
+ const { name, description, ...rest } = this.frontmatter;
64795
+ return new RulesyncSubagent({
64796
+ outputRoot: ".",
64797
+ frontmatter: {
64798
+ targets: ["*"],
64799
+ name,
64800
+ description,
64801
+ commandcode: { ...rest }
64802
+ },
64803
+ body: this.body,
64804
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
64805
+ relativeFilePath: this.getRelativeFilePath(),
64806
+ validate: true
64807
+ });
64808
+ }
64809
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
64810
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
64811
+ const commandcodeSection = rulesyncFrontmatter.commandcode ?? {};
64812
+ const commandcodeSubagentFrontmatter = {
64813
+ name: rulesyncFrontmatter.name,
64814
+ description: rulesyncFrontmatter.description,
64815
+ ...commandcodeSection
64816
+ };
64817
+ const body = rulesyncSubagent.getBody();
64818
+ const fileContent = stringifyFrontmatter(body, commandcodeSubagentFrontmatter, { avoidBlockScalars: true });
64819
+ const paths = this.getSettablePaths({ global });
64820
+ return new CommandcodeSubagent({
64821
+ outputRoot,
64822
+ frontmatter: commandcodeSubagentFrontmatter,
64823
+ body,
64824
+ relativeDirPath: paths.relativeDirPath,
64825
+ relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
64826
+ fileContent,
64827
+ validate,
64828
+ global
64829
+ });
64830
+ }
64831
+ validate() {
64832
+ if (!this.frontmatter) return {
64833
+ success: true,
64834
+ error: null
64835
+ };
64836
+ const result = CommandcodeSubagentFrontmatterSchema.safeParse(this.frontmatter);
64837
+ if (result.success) return {
64838
+ success: true,
64839
+ error: null
64840
+ };
64841
+ else return {
64842
+ success: false,
64843
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
64844
+ };
64845
+ }
64846
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
64847
+ return this.isTargetedByRulesyncSubagentDefault({
64848
+ rulesyncSubagent,
64849
+ toolTarget: "commandcode"
64850
+ });
64851
+ }
64852
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
64853
+ const paths = this.getSettablePaths({ global });
64854
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
64855
+ const fileContent = await readFileContent(filePath);
64856
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
64857
+ const result = CommandcodeSubagentFrontmatterSchema.safeParse(frontmatter);
64858
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
64859
+ return new CommandcodeSubagent({
64860
+ outputRoot,
64861
+ relativeDirPath: paths.relativeDirPath,
64862
+ relativeFilePath,
64863
+ frontmatter: result.data,
64864
+ body: content.trim(),
64865
+ fileContent,
64866
+ validate,
64867
+ global
64868
+ });
64869
+ }
64870
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
64871
+ return new CommandcodeSubagent({
64872
+ outputRoot,
64873
+ relativeDirPath,
64874
+ relativeFilePath,
64875
+ frontmatter: {
64876
+ name: "",
64877
+ description: ""
64878
+ },
64879
+ body: "",
64880
+ fileContent: "",
64881
+ validate: false
64882
+ });
64883
+ }
64884
+ };
64885
+ //#endregion
63405
64886
  //#region src/features/subagents/copilot-subagent.ts
63406
64887
  const CopilotSubagentFrontmatterSchema = z.looseObject({
63407
64888
  name: z.string(),
@@ -67060,6 +68541,15 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
67060
68541
  filePattern: "*.toml"
67061
68542
  }
67062
68543
  }],
68544
+ ["commandcode", {
68545
+ class: CommandcodeSubagent,
68546
+ meta: {
68547
+ supportsProject: true,
68548
+ supportsSimulated: false,
68549
+ supportsGlobal: true,
68550
+ filePattern: "*.md"
68551
+ }
68552
+ }],
67063
68553
  ["copilot", {
67064
68554
  class: CopilotSubagent,
67065
68555
  meta: {
@@ -69892,6 +71382,73 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
69892
71382
  }
69893
71383
  };
69894
71384
  //#endregion
71385
+ //#region src/features/rules/commandcode-rule.ts
71386
+ var CommandcodeRule = class CommandcodeRule extends ToolRule {
71387
+ constructor({ fileContent, root, ...rest }) {
71388
+ super({
71389
+ ...rest,
71390
+ fileContent,
71391
+ root: root ?? false
71392
+ });
71393
+ }
71394
+ static getSettablePaths({ global = false } = {}) {
71395
+ return { root: {
71396
+ relativeDirPath: global ? COMMANDCODE_DIR : ".",
71397
+ relativeFilePath: COMMANDCODE_RULE_FILE_NAME
71398
+ } };
71399
+ }
71400
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
71401
+ const { root } = this.getSettablePaths({ global });
71402
+ const relativePath = join(root.relativeDirPath, root.relativeFilePath);
71403
+ const fileContent = await readFileContent(join(outputRoot, relativePath));
71404
+ return new CommandcodeRule({
71405
+ outputRoot,
71406
+ relativeDirPath: root.relativeDirPath,
71407
+ relativeFilePath: root.relativeFilePath,
71408
+ fileContent,
71409
+ validate,
71410
+ root: true
71411
+ });
71412
+ }
71413
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
71414
+ const { root } = this.getSettablePaths({ global });
71415
+ const isRoot = rulesyncRule.getFrontmatter().root ?? false;
71416
+ return new CommandcodeRule({
71417
+ outputRoot,
71418
+ relativeDirPath: root.relativeDirPath,
71419
+ relativeFilePath: root.relativeFilePath,
71420
+ fileContent: rulesyncRule.getBody(),
71421
+ validate,
71422
+ root: isRoot
71423
+ });
71424
+ }
71425
+ toRulesyncRule() {
71426
+ return this.toRulesyncRuleDefault();
71427
+ }
71428
+ validate() {
71429
+ return {
71430
+ success: true,
71431
+ error: null
71432
+ };
71433
+ }
71434
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
71435
+ return new CommandcodeRule({
71436
+ outputRoot,
71437
+ relativeDirPath,
71438
+ relativeFilePath,
71439
+ fileContent: "",
71440
+ validate: false,
71441
+ root: relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === ".commandcode")
71442
+ });
71443
+ }
71444
+ static isTargetedByRulesyncRule(rulesyncRule) {
71445
+ return this.isTargetedByRulesyncRuleDefault({
71446
+ rulesyncRule,
71447
+ toolTarget: "commandcode"
71448
+ });
71449
+ }
71450
+ };
71451
+ //#endregion
69895
71452
  //#region src/features/rules/continue-rule.ts
69896
71453
  /**
69897
71454
  * Frontmatter schema for Continue rule files (`.continue/rules/*.md`).
@@ -74212,6 +75769,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
74212
75769
  collisionPolicy: "fold"
74213
75770
  }
74214
75771
  }],
75772
+ ["commandcode", {
75773
+ class: CommandcodeRule,
75774
+ meta: {
75775
+ extension: "md",
75776
+ supportsGlobal: true,
75777
+ ruleDiscoveryMode: "auto",
75778
+ collisionPolicy: "fold"
75779
+ }
75780
+ }],
74215
75781
  ["continue", {
74216
75782
  class: ContinueRule,
74217
75783
  meta: {
@@ -77708,4 +79274,4 @@ async function importChecksCore(params) {
77708
79274
  //#endregion
77709
79275
  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, HooksProcessor 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, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_MCP_FILE_NAME as yn, withFallbackLoggerTarget as yt, RulesyncSubagentFrontmatterSchema as z, formatError as zn, listFilePathsRecursively as zt };
77710
79276
 
77711
- //# sourceMappingURL=import-C6R-lXFF.js.map
79277
+ //# sourceMappingURL=import-De13gNpF.js.map