rulesync 16.30.2 → 16.31.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.
@@ -422,6 +422,8 @@ const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim())
422
422
  const { join: join$1 } = posix;
423
423
  const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
424
424
  const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
425
+ const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
426
+ const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
425
427
  const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
426
428
  const RULES_FEATURE_SUBDIR = "rules";
427
429
  const CURATED_RULES_FEATURE_SUBDIR = join$1(RULES_FEATURE_SUBDIR, ".curated");
@@ -470,6 +472,7 @@ const rulesProcessorToolTargetTuple = [
470
472
  "antigravity-plugin",
471
473
  "augmentcode",
472
474
  "augmentcode-legacy",
475
+ "bob",
473
476
  "claudecode",
474
477
  "claudecode-legacy",
475
478
  "cline",
@@ -477,6 +480,7 @@ const rulesProcessorToolTargetTuple = [
477
480
  "codexcli",
478
481
  "copilot",
479
482
  "copilotcli",
483
+ "cortexcode",
480
484
  "crush",
481
485
  "cursor",
482
486
  "deepagents",
@@ -498,6 +502,7 @@ const rulesProcessorToolTargetTuple = [
498
502
  "replit",
499
503
  "roo",
500
504
  "rovodev",
505
+ "tabnine",
501
506
  "takt",
502
507
  "vibe",
503
508
  "warp",
@@ -512,6 +517,7 @@ const ignoreProcessorToolTargetTuple = [
512
517
  "aiassistant",
513
518
  "antigravity-cli",
514
519
  "augmentcode",
520
+ "bob",
515
521
  "claudecode",
516
522
  "claudecode-legacy",
517
523
  "cline",
@@ -526,6 +532,7 @@ const ignoreProcessorToolTargetTuple = [
526
532
  "qwencode",
527
533
  "reasonix",
528
534
  "roo",
535
+ "tabnine",
529
536
  "devin",
530
537
  "vibe",
531
538
  "warp",
@@ -539,6 +546,7 @@ const mcpProcessorToolTargetTuple = [
539
546
  "antigravity-ide",
540
547
  "antigravity-plugin",
541
548
  "augmentcode",
549
+ "bob",
542
550
  "claudecode",
543
551
  "claudecode-plugin",
544
552
  "claudecode-legacy",
@@ -546,6 +554,7 @@ const mcpProcessorToolTargetTuple = [
546
554
  "codexcli",
547
555
  "copilot",
548
556
  "copilotcli",
557
+ "cortexcode",
549
558
  "cursor",
550
559
  "deepagents",
551
560
  "factorydroid",
@@ -564,6 +573,7 @@ const mcpProcessorToolTargetTuple = [
564
573
  "reasonix",
565
574
  "roo",
566
575
  "rovodev",
576
+ "tabnine",
567
577
  "takt",
568
578
  "vibe",
569
579
  "warp",
@@ -577,6 +587,7 @@ const commandsProcessorToolTargetTuple = [
577
587
  "antigravity-cli",
578
588
  "antigravity-ide",
579
589
  "augmentcode",
590
+ "bob",
580
591
  "claudecode",
581
592
  "claudecode-plugin",
582
593
  "claudecode-legacy",
@@ -600,6 +611,7 @@ const commandsProcessorToolTargetTuple = [
600
611
  "roo",
601
612
  "zoocode",
602
613
  "rovodev",
614
+ "tabnine",
603
615
  "takt",
604
616
  "devin",
605
617
  "warp",
@@ -619,6 +631,7 @@ const subagentsProcessorToolTargetTuple = [
619
631
  "codexcli",
620
632
  "copilot",
621
633
  "copilotcli",
634
+ "cortexcode",
622
635
  "cursor",
623
636
  "deepagents",
624
637
  "devin",
@@ -637,6 +650,7 @@ const subagentsProcessorToolTargetTuple = [
637
650
  "roo",
638
651
  "zoocode",
639
652
  "rovodev",
653
+ "tabnine",
640
654
  "takt",
641
655
  "vibe",
642
656
  "zcode"
@@ -650,6 +664,7 @@ const skillsProcessorToolTargetTuple = [
650
664
  "antigravity-ide",
651
665
  "antigravity-plugin",
652
666
  "augmentcode",
667
+ "bob",
653
668
  "claudecode",
654
669
  "claudecode-plugin",
655
670
  "claudecode-legacy",
@@ -657,6 +672,7 @@ const skillsProcessorToolTargetTuple = [
657
672
  "codexcli",
658
673
  "copilot",
659
674
  "copilotcli",
675
+ "cortexcode",
660
676
  "crush",
661
677
  "cursor",
662
678
  "deepagents",
@@ -678,6 +694,7 @@ const skillsProcessorToolTargetTuple = [
678
694
  "replit",
679
695
  "roo",
680
696
  "rovodev",
697
+ "tabnine",
681
698
  "takt",
682
699
  "vibe",
683
700
  "warp",
@@ -700,6 +717,7 @@ const hooksProcessorToolTargetTuple = [
700
717
  "codexcli",
701
718
  "copilot",
702
719
  "copilotcli",
720
+ "cortexcode",
703
721
  "opencode",
704
722
  "pi",
705
723
  "factorydroid",
@@ -712,10 +730,12 @@ const hooksProcessorToolTargetTuple = [
712
730
  "kiro-ide",
713
731
  "devin",
714
732
  "augmentcode",
733
+ "bob",
715
734
  "junie",
716
735
  "vibe",
717
736
  "qwencode",
718
737
  "reasonix",
738
+ "tabnine",
719
739
  "grokcli",
720
740
  "zcode"
721
741
  ];
@@ -748,6 +768,7 @@ const permissionsProcessorToolTargetTuple = [
748
768
  "reasonix",
749
769
  "roo",
750
770
  "rovodev",
771
+ "tabnine",
751
772
  "takt",
752
773
  "vibe",
753
774
  "warp",
@@ -3233,16 +3254,21 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
3233
3254
  ".amp/settings.json",
3234
3255
  ".amp/settings.jsonc",
3235
3256
  ".antigravity/settings.json",
3257
+ ".bob/settings.json",
3258
+ ".bob/settings/settings.json",
3236
3259
  ".claude/settings.json",
3237
3260
  ".claude/settings.local.json",
3238
3261
  ".codex/config.toml",
3239
3262
  ".copilot/settings.json",
3240
3263
  ".github/copilot/settings.json",
3264
+ ".cortex/settings.json",
3265
+ ".snowflake/cortex/hooks.json",
3241
3266
  ".devin/config.json",
3242
3267
  ".factory/settings.json",
3243
3268
  ".grok/config.toml",
3244
3269
  ".rovodev/config.yml",
3245
3270
  ".rovodev/mcp.json",
3271
+ ".tabnine/agent/settings.json",
3246
3272
  ".vibe/config.toml",
3247
3273
  ".zcode/config.json",
3248
3274
  ".zcode/cli/config.json",
@@ -4753,6 +4779,110 @@ const CANONICAL_TO_ZCODE_EVENT_NAMES = {
4753
4779
  };
4754
4780
  const ZCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4755
4781
  /**
4782
+ * Hook events supported by IBM Bob's lifecycle hooks.
4783
+ *
4784
+ * Bob reads `hooks` from `.bob/settings.json` (project) and
4785
+ * `~/.bob/settings/settings.json` (user) in the Claude-Code shape and fires
4786
+ * five events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse and
4787
+ * Stop. Only `command` hooks exist, and `matcher` (a regex over the tool
4788
+ * name) applies to the two tool events.
4789
+ *
4790
+ * @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
4791
+ */
4792
+ const BOB_HOOK_EVENTS = [
4793
+ "sessionStart",
4794
+ "beforeSubmitPrompt",
4795
+ "preToolUse",
4796
+ "postToolUse",
4797
+ "stop"
4798
+ ];
4799
+ const CANONICAL_TO_BOB_EVENT_NAMES = {
4800
+ sessionStart: "SessionStart",
4801
+ beforeSubmitPrompt: "UserPromptSubmit",
4802
+ preToolUse: "PreToolUse",
4803
+ postToolUse: "PostToolUse",
4804
+ stop: "Stop"
4805
+ };
4806
+ const BOB_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_BOB_EVENT_NAMES).map(([k, v]) => [v, k]));
4807
+ /**
4808
+ * Hook events supported by Tabnine CLI.
4809
+ *
4810
+ * Tabnine CLI reads `hooks` from `.tabnine/agent/settings.json` (project) and
4811
+ * `~/.tabnine/agent/settings.json` (user) and fires eleven events with
4812
+ * PascalCase names. Only `command` hooks exist; `matcher` is a regex over the
4813
+ * tool name for the two tool events and an exact string for the lifecycle
4814
+ * events, and `timeout` is expressed in milliseconds.
4815
+ *
4816
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks
4817
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks/configuration
4818
+ */
4819
+ const TABNINE_HOOK_EVENTS = [
4820
+ "sessionStart",
4821
+ "sessionEnd",
4822
+ "beforeSubmitPrompt",
4823
+ "stop",
4824
+ "preModelInvocation",
4825
+ "postModelInvocation",
4826
+ "beforeToolSelection",
4827
+ "preToolUse",
4828
+ "postToolUse",
4829
+ "preCompact",
4830
+ "notification"
4831
+ ];
4832
+ const CANONICAL_TO_TABNINE_EVENT_NAMES = {
4833
+ sessionStart: "SessionStart",
4834
+ sessionEnd: "SessionEnd",
4835
+ beforeSubmitPrompt: "BeforeAgent",
4836
+ stop: "AfterAgent",
4837
+ preModelInvocation: "BeforeModel",
4838
+ postModelInvocation: "AfterModel",
4839
+ beforeToolSelection: "BeforeToolSelection",
4840
+ preToolUse: "BeforeTool",
4841
+ postToolUse: "AfterTool",
4842
+ preCompact: "PreCompress",
4843
+ notification: "Notification"
4844
+ };
4845
+ const TABNINE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_TABNINE_EVENT_NAMES).map(([k, v]) => [v, k]));
4846
+ /**
4847
+ * Hook events supported by Snowflake Cortex Code.
4848
+ *
4849
+ * Cortex Code reads `hooks` from `.cortex/settings.json` (project) and
4850
+ * `~/.snowflake/cortex/hooks.json` (user) in the Claude-Code-shaped layout:
4851
+ * PascalCase event names, a regex `matcher` over the tool name, and `command`
4852
+ * or `prompt` hooks with a `timeout` in seconds and an optional `enabled`
4853
+ * flag. `$CORTEX_PROJECT_DIR` resolves to the project root inside commands.
4854
+ *
4855
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility
4856
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/settings
4857
+ */
4858
+ const CORTEXCODE_HOOK_EVENTS = [
4859
+ "preToolUse",
4860
+ "postToolUse",
4861
+ "permissionRequest",
4862
+ "beforeSubmitPrompt",
4863
+ "sessionStart",
4864
+ "sessionEnd",
4865
+ "preCompact",
4866
+ "stop",
4867
+ "subagentStop",
4868
+ "notification",
4869
+ "setup"
4870
+ ];
4871
+ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
4872
+ preToolUse: "PreToolUse",
4873
+ postToolUse: "PostToolUse",
4874
+ permissionRequest: "PermissionRequest",
4875
+ beforeSubmitPrompt: "UserPromptSubmit",
4876
+ sessionStart: "SessionStart",
4877
+ sessionEnd: "SessionEnd",
4878
+ preCompact: "PreCompact",
4879
+ stop: "Stop",
4880
+ subagentStop: "SubagentStop",
4881
+ notification: "Notification",
4882
+ setup: "Setup"
4883
+ };
4884
+ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4885
+ /**
4756
4886
  * Hook events supported by Hermes Agent's native Shell Hooks system.
4757
4887
  *
4758
4888
  * Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
@@ -4905,6 +5035,9 @@ const HooksConfigSchema = z.looseObject({
4905
5035
  grokcli: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
4906
5036
  "kimi-code": z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
4907
5037
  zcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5038
+ bob: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5039
+ cortexcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
5040
+ tabnine: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
4908
5041
  qwencode: z.optional(z.looseObject({
4909
5042
  hooks: z.optional(hooksRecordSchema),
4910
5043
  disableAllHooks: z.optional(z.boolean())
@@ -5906,6 +6039,7 @@ const RulesyncMcpFileSchema = z.looseObject({
5906
6039
  "antigravity-ide": z.optional(toolScopedMcpSchema),
5907
6040
  aiassistant: z.optional(toolScopedMcpSchema),
5908
6041
  augmentcode: z.optional(toolScopedMcpSchema),
6042
+ bob: z.optional(toolScopedMcpSchema),
5909
6043
  claudecode: z.optional(toolScopedMcpSchema),
5910
6044
  cline: z.optional(toolScopedMcpSchema),
5911
6045
  codexcli: z.optional(toolScopedMcpSchema),
@@ -5927,6 +6061,8 @@ const RulesyncMcpFileSchema = z.looseObject({
5927
6061
  reasonix: z.optional(toolScopedMcpSchema),
5928
6062
  roo: z.optional(toolScopedMcpSchema),
5929
6063
  rovodev: z.optional(toolScopedMcpSchema),
6064
+ cortexcode: z.optional(toolScopedMcpSchema),
6065
+ tabnine: z.optional(toolScopedMcpSchema),
5930
6066
  takt: z.optional(toolScopedMcpSchema),
5931
6067
  vibe: z.optional(toolScopedMcpSchema),
5932
6068
  warp: z.optional(toolScopedMcpSchema),
@@ -6748,6 +6884,27 @@ const QwencodePermissionsOverrideSchema = z.looseObject({
6748
6884
  autoMode: z.optional(z.looseObject({}))
6749
6885
  });
6750
6886
  /**
6887
+ * Tool-scoped override block for Tabnine CLI. The canonical `permission` block
6888
+ * drives only the `tools.allowed` / `tools.exclude` lists of
6889
+ * `.tabnine/agent/settings.json`; the other `tools.*` keys (`core`,
6890
+ * `enableCoaching`, `useRipgrep`, `shell.*`, `enableWebTools`, ...) and the
6891
+ * `general.defaultApprovalMode` switch have no canonical category, so they are
6892
+ * authored here, deep-merged into the settings file, and emitted only for
6893
+ * Tabnine. Import lifts the entries rulesync cannot map back onto a canonical
6894
+ * category into this block so a regenerate does not drop them. Kept
6895
+ * `looseObject` (verbatim passthrough). Both project and global scope are
6896
+ * supported.
6897
+ *
6898
+ * @example
6899
+ * { "tools": { "core": ["read_file", "run_shell_command(git)"] },
6900
+ * "general": { "defaultApprovalMode": "plan" } }
6901
+ */
6902
+ const TabninePermissionsOverrideSchema = z.looseObject({
6903
+ permission: z.optional(ToolScopedPermissionSchema),
6904
+ tools: z.optional(z.looseObject({})),
6905
+ general: z.optional(z.looseObject({}))
6906
+ });
6907
+ /**
6751
6908
  * Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
6752
6909
  * to per-tool allow/ask/deny with no canonical category — the `[sandbox]`
6753
6910
  * enforcement table (`workspace_root`, `allow_write`, `forbid_read`, `bash`,
@@ -7508,6 +7665,7 @@ const PermissionsConfigSchema = z.looseObject({
7508
7665
  factorydroid: z.optional(FactorydroidPermissionsOverrideSchema),
7509
7666
  warp: z.optional(WarpPermissionsOverrideSchema),
7510
7667
  junie: z.optional(JuniePermissionsOverrideSchema),
7668
+ tabnine: z.optional(TabninePermissionsOverrideSchema),
7511
7669
  takt: z.optional(TaktPermissionsOverrideSchema),
7512
7670
  amp: z.optional(AmpPermissionsOverrideSchema),
7513
7671
  "antigravity-cli": z.optional(AntigravityCliPermissionsOverrideSchema),
@@ -9155,6 +9313,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
9155
9313
  })),
9156
9314
  cline: z.optional(z.looseObject({})),
9157
9315
  roo: z.optional(z.looseObject({})),
9316
+ bob: z.optional(z.looseObject({})),
9158
9317
  amp: z.optional(z.looseObject({})),
9159
9318
  devin: z.optional(z.looseObject({
9160
9319
  "argument-hint": z.optional(z.string()),
@@ -9232,6 +9391,8 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
9232
9391
  "user-invocable": z.optional(z.boolean()),
9233
9392
  "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
9234
9393
  })),
9394
+ cortexcode: z.optional(z.looseObject({})),
9395
+ tabnine: z.optional(z.looseObject({})),
9235
9396
  takt: z.optional(z.looseObject({
9236
9397
  name: z.optional(z.string()),
9237
9398
  extends: z.optional(z.string())
@@ -12794,6 +12955,53 @@ const SHARED_CONFIG_OWNERSHIP = {
12794
12955
  },
12795
12956
  ".zcode/config.json": ZCODE_WORKSPACE_CONFIG_DECLARATION,
12796
12957
  ".zcode/cli/config.json": ZCODE_USER_CONFIG_DECLARATION,
12958
+ ".bob/settings.json": {
12959
+ format: "json",
12960
+ invalidRootPolicy: "error",
12961
+ features: { hooks: {
12962
+ kind: "replace-owned-keys",
12963
+ ownedKeys: ["hooks"]
12964
+ } }
12965
+ },
12966
+ ".bob/settings/settings.json": {
12967
+ format: "json",
12968
+ invalidRootPolicy: "error",
12969
+ features: { hooks: {
12970
+ kind: "replace-owned-keys",
12971
+ ownedKeys: ["hooks"]
12972
+ } }
12973
+ },
12974
+ ".cortex/settings.json": {
12975
+ format: "json",
12976
+ invalidRootPolicy: "error",
12977
+ features: { hooks: {
12978
+ kind: "replace-owned-keys",
12979
+ ownedKeys: ["hooks"]
12980
+ } }
12981
+ },
12982
+ ".snowflake/cortex/hooks.json": {
12983
+ format: "json",
12984
+ invalidRootPolicy: "error",
12985
+ features: { hooks: {
12986
+ kind: "replace-owned-keys",
12987
+ ownedKeys: ["hooks"]
12988
+ } }
12989
+ },
12990
+ ".tabnine/agent/settings.json": {
12991
+ format: "json",
12992
+ invalidRootPolicy: "error",
12993
+ features: {
12994
+ mcp: {
12995
+ kind: "replace-owned-keys",
12996
+ ownedKeys: ["mcpServers"]
12997
+ },
12998
+ hooks: {
12999
+ kind: "replace-owned-keys",
13000
+ ownedKeys: ["hooks"]
13001
+ },
13002
+ permissions: { kind: "deep-merge" }
13003
+ }
13004
+ },
12797
13005
  ".kiro/agents/default.json": {
12798
13006
  format: "json",
12799
13007
  features: {
@@ -14486,6 +14694,140 @@ var AugmentcodeCommand = class AugmentcodeCommand extends ToolCommand {
14486
14694
  }
14487
14695
  };
14488
14696
  //#endregion
14697
+ //#region src/constants/bob-paths.ts
14698
+ const BOB_DIR = ".bob";
14699
+ const BOB_RULE_FILE_NAME = "AGENTS.md";
14700
+ join(BOB_DIR, "rules");
14701
+ const BOB_IGNORE_FILE_NAME = ".bobignore";
14702
+ const BOB_MCP_FILE_NAME = "mcp.json";
14703
+ const BOB_COMMANDS_DIR_PATH = join(BOB_DIR, "commands");
14704
+ const BOB_SKILLS_DIR_PATH = join(BOB_DIR, "skills");
14705
+ const BOB_SETTINGS_FILE_NAME = "settings.json";
14706
+ const BOB_GLOBAL_SETTINGS_DIR_PATH = join(BOB_DIR, "settings");
14707
+ //#endregion
14708
+ //#region src/features/commands/bob-command.ts
14709
+ /**
14710
+ * IBM Bob slash commands are Markdown files under `<project>/.bob/commands/`
14711
+ * (project scope) and `~/.bob/commands/` (user scope), invoked from the chat
14712
+ * input with `/`. The file name is the command name; the optional
14713
+ * `description` / `argument-hint` frontmatter keys are the same ones the
14714
+ * Claude-Code-lineage tools use, and the body is the prompt Bob runs.
14715
+ *
14716
+ * @see https://bob.ibm.com/docs/ide/features/slash-commands
14717
+ * @see https://bob.ibm.com/docs/shell/features/slash-commands
14718
+ */
14719
+ const BobCommandFrontmatterSchema = z.looseObject({
14720
+ description: z.optional(z.string()),
14721
+ "argument-hint": z.optional(z.string())
14722
+ });
14723
+ var BobCommand = class BobCommand extends ToolCommand {
14724
+ frontmatter;
14725
+ body;
14726
+ constructor({ frontmatter, body, ...rest }) {
14727
+ if (rest.validate) {
14728
+ const result = BobCommandFrontmatterSchema.safeParse(frontmatter);
14729
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
14730
+ }
14731
+ super({
14732
+ ...rest,
14733
+ fileContent: stringifyFrontmatter(body, frontmatter)
14734
+ });
14735
+ this.frontmatter = frontmatter;
14736
+ this.body = body;
14737
+ }
14738
+ static getSettablePaths(_options = {}) {
14739
+ return { relativeDirPath: BOB_COMMANDS_DIR_PATH };
14740
+ }
14741
+ getBody() {
14742
+ return this.body;
14743
+ }
14744
+ getFrontmatter() {
14745
+ return this.frontmatter;
14746
+ }
14747
+ toRulesyncCommand() {
14748
+ const { description, ...restFields } = this.frontmatter;
14749
+ const rulesyncFrontmatter = {
14750
+ targets: ["*"],
14751
+ description,
14752
+ ...Object.keys(restFields).length > 0 && { bob: restFields }
14753
+ };
14754
+ const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
14755
+ return new RulesyncCommand({
14756
+ outputRoot: ".",
14757
+ frontmatter: rulesyncFrontmatter,
14758
+ body: this.body,
14759
+ relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
14760
+ relativeFilePath: this.relativeFilePath,
14761
+ fileContent,
14762
+ validate: true
14763
+ });
14764
+ }
14765
+ static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
14766
+ const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
14767
+ const bobFields = rulesyncFrontmatter.bob ?? {};
14768
+ const bobFrontmatter = {
14769
+ description: rulesyncFrontmatter.description,
14770
+ ...bobFields
14771
+ };
14772
+ const body = rulesyncCommand.getBody();
14773
+ const paths = this.getSettablePaths({ global });
14774
+ return new BobCommand({
14775
+ outputRoot,
14776
+ frontmatter: bobFrontmatter,
14777
+ body,
14778
+ relativeDirPath: paths.relativeDirPath,
14779
+ relativeFilePath: rulesyncCommand.getRelativeFilePath(),
14780
+ validate
14781
+ });
14782
+ }
14783
+ validate() {
14784
+ if (!this.frontmatter) return {
14785
+ success: true,
14786
+ error: null
14787
+ };
14788
+ const result = BobCommandFrontmatterSchema.safeParse(this.frontmatter);
14789
+ if (result.success) return {
14790
+ success: true,
14791
+ error: null
14792
+ };
14793
+ return {
14794
+ success: false,
14795
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
14796
+ };
14797
+ }
14798
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
14799
+ return this.isTargetedByRulesyncCommandDefault({
14800
+ rulesyncCommand,
14801
+ toolTarget: "bob"
14802
+ });
14803
+ }
14804
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
14805
+ const paths = this.getSettablePaths({ global });
14806
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
14807
+ const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
14808
+ const result = BobCommandFrontmatterSchema.safeParse(frontmatter);
14809
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
14810
+ return new BobCommand({
14811
+ outputRoot,
14812
+ relativeDirPath: paths.relativeDirPath,
14813
+ relativeFilePath,
14814
+ frontmatter: result.data,
14815
+ body: content.trim(),
14816
+ validate
14817
+ });
14818
+ }
14819
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
14820
+ return new BobCommand({
14821
+ outputRoot,
14822
+ relativeDirPath,
14823
+ relativeFilePath,
14824
+ frontmatter: { description: "" },
14825
+ body: "",
14826
+ validate: false
14827
+ });
14828
+ }
14829
+ };
14830
+ //#endregion
14489
14831
  //#region src/features/commands/claudecode-command.ts
14490
14832
  const ClaudecodeCommandFrontmatterSchema = z.looseObject({
14491
14833
  description: z.optional(z.string()),
@@ -18212,6 +18554,193 @@ var RovodevPromptsManifest = class extends ToolFile {
18212
18554
  }
18213
18555
  };
18214
18556
  //#endregion
18557
+ //#region src/constants/tabnine-paths.ts
18558
+ const TABNINE_DIR = ".tabnine";
18559
+ const TABNINE_AGENT_DIR_PATH = join(TABNINE_DIR, "agent");
18560
+ const TABNINE_RULE_FILE_NAME = "TABNINE.md";
18561
+ join(TABNINE_DIR, "guidelines");
18562
+ const TABNINE_IGNORE_FILE_NAME = ".tabnineignore";
18563
+ const TABNINE_SETTINGS_FILE_NAME = "settings.json";
18564
+ const TABNINE_COMMANDS_DIR_PATH = join(TABNINE_AGENT_DIR_PATH, "commands");
18565
+ const TABNINE_AGENTS_DIR_PATH = join(TABNINE_AGENT_DIR_PATH, "agents");
18566
+ const TABNINE_SKILLS_DIR_PATH = join(TABNINE_AGENT_DIR_PATH, "skills");
18567
+ //#endregion
18568
+ //#region src/features/commands/tabnine-command.ts
18569
+ const TabnineCommandFrontmatterSchema = z.looseObject({
18570
+ description: z.optional(z.string()),
18571
+ prompt: z.string()
18572
+ });
18573
+ /**
18574
+ * Translate rulesync universal command syntax (Claude Code compatible) into
18575
+ * Tabnine CLI's native syntax. See docs/reference/command-syntax.md. Tabnine
18576
+ * CLI is a Gemini CLI derivative and documents the same placeholders:
18577
+ * `{{args}}` for the user's arguments and `!{cmd}` for shell injection. Its
18578
+ * `@{path}` file-injection form has no universal counterpart and passes
18579
+ * through untouched in both directions.
18580
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/commands
18581
+ *
18582
+ * Replacement order:
18583
+ * 1. `` !`cmd` `` → `!{cmd}` (backtick shell expansion → brace form)
18584
+ * 2. `$ARGUMENTS` → `{{args}}` (handled after step 1 so that
18585
+ * `` !`echo $ARGUMENTS` `` survives as `!{echo {{args}}}` rather than
18586
+ * requiring two passes).
18587
+ *
18588
+ * `$ARGUMENTS\b` uses a trailing word boundary so `$ARGUMENTSx` and
18589
+ * `$ARGUMENTS_FOO` are left alone, while `$ARGUMENTS-foo` (hyphen is not a
18590
+ * word char) is rewritten. There is no leading anchor, so `prefix$ARGUMENTS`
18591
+ * is rewritten to `prefix{{args}}`.
18592
+ *
18593
+ * Bodies that already contain Tabnine-native forms (`{{args}}` or `!{cmd}`)
18594
+ * are left untouched, which gives us the documented "we do not re-translate
18595
+ * already-Tabnine-native forms" property.
18596
+ */
18597
+ function translateRulesyncBodyToTabnine(body) {
18598
+ return body.replace(/!`([^`\n]+)`/g, "!{$1}").replace(/\$ARGUMENTS\b/g, "{{args}}");
18599
+ }
18600
+ /**
18601
+ * Inverse of {@link translateRulesyncBodyToTabnine}, used when importing a
18602
+ * Tabnine CLI command file back into rulesync's universal syntax.
18603
+ *
18604
+ * Replacement order is intentionally inverted from the forward direction:
18605
+ * 1. `{{args}}` → `$ARGUMENTS` (handled first)
18606
+ * 2. `!{cmd}` → `` !`cmd` `` (handled second, with a non-greedy body
18607
+ * `[^}\n]+?`)
18608
+ *
18609
+ * Doing `{{args}}` first ensures that nested forms like
18610
+ * `!{echo {{args}}}` round-trip back to `` !`echo $ARGUMENTS` `` in a single
18611
+ * pass: the inner `{{args}}` is rewritten to `$ARGUMENTS`, and then the
18612
+ * non-greedy `!{...}` match consumes the smallest possible body.
18613
+ */
18614
+ function translateTabnineBodyToRulesync(body) {
18615
+ return body.replace(/\{\{\s*args\s*\}\}/g, "$ARGUMENTS").replace(/!\{([^}\n]+?)\}/g, "!`$1`");
18616
+ }
18617
+ /**
18618
+ * Tabnine CLI custom slash commands: TOML files with `description` and
18619
+ * `prompt` under `<project>/.tabnine/agent/commands/` (project) and
18620
+ * `~/.tabnine/agent/commands/` (user). The relative path names the command:
18621
+ * `review.toml` → `/review`, `code/review.toml` → `/code:review`.
18622
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/commands
18623
+ */
18624
+ var TabnineCommand = class TabnineCommand extends ToolCommand {
18625
+ frontmatter;
18626
+ body;
18627
+ constructor(params) {
18628
+ super(params);
18629
+ const parsed = this.parseTomlContent(this.fileContent);
18630
+ this.frontmatter = parsed;
18631
+ this.body = parsed.prompt;
18632
+ }
18633
+ static getSettablePaths(_options = {}) {
18634
+ return { relativeDirPath: TABNINE_COMMANDS_DIR_PATH };
18635
+ }
18636
+ parseTomlContent(content) {
18637
+ try {
18638
+ const parsed = parse$1(content);
18639
+ const result = TabnineCommandFrontmatterSchema.safeParse(parsed);
18640
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`);
18641
+ return {
18642
+ ...result.data,
18643
+ description: result.data.description
18644
+ };
18645
+ } catch (error) {
18646
+ throw new Error(`Failed to parse TOML command file (${join(this.relativeDirPath, this.relativeFilePath)}): ${formatError(error)}`, { cause: error });
18647
+ }
18648
+ }
18649
+ getBody() {
18650
+ return this.body;
18651
+ }
18652
+ getFrontmatter() {
18653
+ return {
18654
+ description: this.frontmatter.description,
18655
+ prompt: this.frontmatter.prompt
18656
+ };
18657
+ }
18658
+ toRulesyncCommand() {
18659
+ const { description, prompt: _prompt, ...restFields } = this.frontmatter;
18660
+ const rulesyncFrontmatter = {
18661
+ targets: ["tabnine"],
18662
+ description,
18663
+ ...Object.keys(restFields).length > 0 && { tabnine: restFields }
18664
+ };
18665
+ const universalBody = translateTabnineBodyToRulesync(this.body);
18666
+ const fileContent = stringifyFrontmatter(universalBody, rulesyncFrontmatter);
18667
+ return new RulesyncCommand({
18668
+ outputRoot: process.cwd(),
18669
+ frontmatter: rulesyncFrontmatter,
18670
+ body: universalBody,
18671
+ relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
18672
+ relativeFilePath: this.relativeFilePath.replace(/\.toml$/, ".md"),
18673
+ fileContent,
18674
+ validate: true
18675
+ });
18676
+ }
18677
+ static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
18678
+ const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
18679
+ const tabnineFields = rulesyncFrontmatter.tabnine ?? {};
18680
+ const translatedPrompt = typeof tabnineFields.prompt === "string" ? "" : translateRulesyncBodyToTabnine(rulesyncCommand.getBody());
18681
+ const tabnineFrontmatter = {
18682
+ description: rulesyncFrontmatter.description,
18683
+ prompt: translatedPrompt,
18684
+ ...tabnineFields
18685
+ };
18686
+ const tomlObject = {};
18687
+ if (tabnineFrontmatter.description !== void 0) tomlObject.description = tabnineFrontmatter.description;
18688
+ tomlObject.prompt = tabnineFrontmatter.prompt.endsWith("\n") ? tabnineFrontmatter.prompt : `${tabnineFrontmatter.prompt}\n`;
18689
+ const { description: _description, prompt: _prompt, ...extraFields } = tabnineFrontmatter;
18690
+ for (const [key, value] of Object.entries(omitPrototypePollutionKeys(extraFields))) if (value !== void 0) tomlObject[key] = value;
18691
+ const tomlContent = stringify(tomlObject);
18692
+ const paths = this.getSettablePaths({ global });
18693
+ return new TabnineCommand({
18694
+ outputRoot,
18695
+ relativeDirPath: paths.relativeDirPath,
18696
+ relativeFilePath: rulesyncCommand.getRelativeFilePath().replace(/\.md$/, ".toml"),
18697
+ fileContent: tomlContent,
18698
+ validate
18699
+ });
18700
+ }
18701
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
18702
+ const paths = this.getSettablePaths({ global });
18703
+ const fileContent = await readFileContent(join(outputRoot, paths.relativeDirPath, relativeFilePath));
18704
+ return new TabnineCommand({
18705
+ outputRoot,
18706
+ relativeDirPath: paths.relativeDirPath,
18707
+ relativeFilePath,
18708
+ fileContent,
18709
+ validate
18710
+ });
18711
+ }
18712
+ validate() {
18713
+ try {
18714
+ this.parseTomlContent(this.fileContent);
18715
+ return {
18716
+ success: true,
18717
+ error: null
18718
+ };
18719
+ } catch (error) {
18720
+ return {
18721
+ success: false,
18722
+ error: error instanceof Error ? error : new Error(String(error))
18723
+ };
18724
+ }
18725
+ }
18726
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
18727
+ return this.isTargetedByRulesyncCommandDefault({
18728
+ rulesyncCommand,
18729
+ toolTarget: "tabnine"
18730
+ });
18731
+ }
18732
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
18733
+ return new TabnineCommand({
18734
+ outputRoot,
18735
+ relativeDirPath,
18736
+ relativeFilePath,
18737
+ fileContent: `description = ""
18738
+ prompt = ""`,
18739
+ validate: false
18740
+ });
18741
+ }
18742
+ };
18743
+ //#endregion
18215
18744
  //#region src/features/takt-shared.ts
18216
18745
  /**
18217
18746
  * Shared utilities for all TAKT-* tool file classes.
@@ -18683,6 +19212,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
18683
19212
  matchAdditionalImportsByBasename: true
18684
19213
  }
18685
19214
  }],
19215
+ ["bob", {
19216
+ class: BobCommand,
19217
+ meta: {
19218
+ extension: "md",
19219
+ supportsProject: true,
19220
+ supportsGlobal: true,
19221
+ isSimulated: false,
19222
+ supportsSubdirectory: true
19223
+ }
19224
+ }],
18686
19225
  ["claudecode", {
18687
19226
  class: ClaudecodeCommand,
18688
19227
  meta: {
@@ -18914,6 +19453,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
18914
19453
  supportsSubdirectory: false
18915
19454
  }
18916
19455
  }],
19456
+ ["tabnine", {
19457
+ class: TabnineCommand,
19458
+ meta: {
19459
+ extension: "toml",
19460
+ supportsProject: true,
19461
+ supportsGlobal: true,
19462
+ isSimulated: false,
19463
+ supportsSubdirectory: true
19464
+ }
19465
+ }],
18917
19466
  ["takt", {
18918
19467
  class: TaktCommand,
18919
19468
  meta: {
@@ -20953,6 +21502,135 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
20953
21502
  }
20954
21503
  };
20955
21504
  //#endregion
21505
+ //#region src/features/hooks/bob-hooks.ts
21506
+ const BOB_CONVERTER_CONFIG = {
21507
+ supportedEvents: BOB_HOOK_EVENTS,
21508
+ canonicalToToolEventNames: CANONICAL_TO_BOB_EVENT_NAMES,
21509
+ toolToCanonicalEventNames: BOB_TO_CANONICAL_EVENT_NAMES,
21510
+ projectDirVar: "",
21511
+ noMatcherEvents: /* @__PURE__ */ new Set([
21512
+ "sessionStart",
21513
+ "beforeSubmitPrompt",
21514
+ "stop"
21515
+ ]),
21516
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
21517
+ wildcardMatcherMeansAll: true
21518
+ };
21519
+ /**
21520
+ * Single spelling of the settings.json codec/policy: fail closed on an
21521
+ * unparseable root rather than replacing the user's Bob settings with
21522
+ * generated output.
21523
+ */
21524
+ function parseBobSettings(fileContent, filePath) {
21525
+ return parseSharedConfig({
21526
+ format: "json",
21527
+ fileContent,
21528
+ filePath,
21529
+ invalidRootPolicy: "error"
21530
+ });
21531
+ }
21532
+ /**
21533
+ * IBM Bob lifecycle hooks.
21534
+ *
21535
+ * Hooks live under the top-level `hooks` key of Bob's settings file —
21536
+ * `<project>/.bob/settings.json` (project scope) and
21537
+ * `~/.bob/settings/settings.json` (user scope) — in the Claude-Code shape:
21538
+ * `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{ "type": "command",
21539
+ * "command": "...", "timeout"?: <seconds> }] }] }`. The file also holds
21540
+ * settings rulesync does not own, so generation merges the `hooks` key into it
21541
+ * (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting the file.
21542
+ *
21543
+ * @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
21544
+ */
21545
+ var BobHooks = class BobHooks extends ToolHooks {
21546
+ constructor(params) {
21547
+ super({
21548
+ ...params,
21549
+ fileContent: params.fileContent ?? "{}"
21550
+ });
21551
+ }
21552
+ isDeletable() {
21553
+ return false;
21554
+ }
21555
+ static getSettablePaths({ global = false } = {}) {
21556
+ return {
21557
+ relativeDirPath: global ? BOB_GLOBAL_SETTINGS_DIR_PATH : BOB_DIR,
21558
+ relativeFilePath: BOB_SETTINGS_FILE_NAME
21559
+ };
21560
+ }
21561
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
21562
+ const paths = BobHooks.getSettablePaths({ global });
21563
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
21564
+ return new BobHooks({
21565
+ outputRoot,
21566
+ relativeDirPath: paths.relativeDirPath,
21567
+ relativeFilePath: paths.relativeFilePath,
21568
+ fileContent,
21569
+ validate
21570
+ });
21571
+ }
21572
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
21573
+ const paths = BobHooks.getSettablePaths({ global });
21574
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
21575
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
21576
+ const config = rulesyncHooks.getJson();
21577
+ const hooks = canonicalToToolHooks({
21578
+ config,
21579
+ toolOverrideHooks: config.bob?.hooks,
21580
+ converterConfig: BOB_CONVERTER_CONFIG,
21581
+ logger
21582
+ });
21583
+ const fileContent = applySharedConfigPatch({
21584
+ fileKey: sharedConfigFileKey(paths),
21585
+ feature: "hooks",
21586
+ existingContent,
21587
+ patch: { hooks },
21588
+ filePath,
21589
+ logger
21590
+ });
21591
+ return new BobHooks({
21592
+ outputRoot,
21593
+ relativeDirPath: paths.relativeDirPath,
21594
+ relativeFilePath: paths.relativeFilePath,
21595
+ fileContent,
21596
+ validate
21597
+ });
21598
+ }
21599
+ toRulesyncHooks({ logger } = {}) {
21600
+ const configPath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
21601
+ let settings;
21602
+ try {
21603
+ settings = parseBobSettings(this.getFileContent(), configPath);
21604
+ } catch (error) {
21605
+ throw new Error(`Failed to parse Bob hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
21606
+ }
21607
+ const hooks = toolHooksToCanonical({
21608
+ logger,
21609
+ hooks: settings.hooks,
21610
+ converterConfig: BOB_CONVERTER_CONFIG
21611
+ });
21612
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
21613
+ hooks,
21614
+ overrideKey: "bob"
21615
+ }), null, 2) });
21616
+ }
21617
+ validate() {
21618
+ return {
21619
+ success: true,
21620
+ error: null
21621
+ };
21622
+ }
21623
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
21624
+ return new BobHooks({
21625
+ outputRoot,
21626
+ relativeDirPath,
21627
+ relativeFilePath,
21628
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
21629
+ validate: false
21630
+ });
21631
+ }
21632
+ };
21633
+ //#endregion
20956
21634
  //#region src/features/hooks/preserve-unowned-hook-commands.ts
20957
21635
  /**
20958
21636
  * Read the `hooks` value from a destination JSON file.
@@ -22518,6 +23196,151 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
22518
23196
  }
22519
23197
  };
22520
23198
  //#endregion
23199
+ //#region src/constants/cortexcode-paths.ts
23200
+ const CORTEXCODE_DIR = ".cortex";
23201
+ const CORTEXCODE_GLOBAL_DIR_PATH = join(".snowflake", "cortex");
23202
+ const CORTEXCODE_RULE_FILE_NAME = "AGENTS.md";
23203
+ const CORTEXCODE_SETTINGS_FILE_NAME = "settings.json";
23204
+ const CORTEXCODE_GLOBAL_HOOKS_FILE_NAME = "hooks.json";
23205
+ const CORTEXCODE_MCP_FILE_NAME = "mcp.json";
23206
+ const CORTEXCODE_AGENTS_DIR_PATH = join(CORTEXCODE_DIR, "agents");
23207
+ const CORTEXCODE_GLOBAL_AGENTS_DIR_PATH = join(CORTEXCODE_GLOBAL_DIR_PATH, "agents");
23208
+ const CORTEXCODE_SKILLS_DIR_PATH = join(CORTEXCODE_DIR, "skills");
23209
+ const CORTEXCODE_GLOBAL_SKILLS_DIR_PATH = join(CORTEXCODE_GLOBAL_DIR_PATH, "skills");
23210
+ //#endregion
23211
+ //#region src/features/hooks/cortexcode-hooks.ts
23212
+ const CORTEXCODE_CONVERTER_CONFIG = {
23213
+ supportedEvents: CORTEXCODE_HOOK_EVENTS,
23214
+ canonicalToToolEventNames: CANONICAL_TO_CORTEXCODE_EVENT_NAMES,
23215
+ toolToCanonicalEventNames: CORTEXCODE_TO_CANONICAL_EVENT_NAMES,
23216
+ projectDirVar: "$CORTEX_PROJECT_DIR",
23217
+ prefixDotRelativeCommandsOnly: true,
23218
+ noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
23219
+ supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
23220
+ booleanPassthroughFields: [{
23221
+ canonical: "enabled",
23222
+ tool: "enabled"
23223
+ }]
23224
+ };
23225
+ /**
23226
+ * Single spelling of the settings/hooks codec/policy: fail closed on an
23227
+ * unparseable root rather than replacing the user's Cortex Code settings with
23228
+ * generated output.
23229
+ */
23230
+ function parseCortexcodeSettings(fileContent, filePath) {
23231
+ return parseSharedConfig({
23232
+ format: "json",
23233
+ fileContent,
23234
+ filePath,
23235
+ invalidRootPolicy: "error"
23236
+ });
23237
+ }
23238
+ /**
23239
+ * Snowflake Cortex Code hooks.
23240
+ *
23241
+ * Hooks live under the top-level `hooks` key of `<project>/.cortex/settings.json`
23242
+ * (project scope) and of the dedicated `~/.snowflake/cortex/hooks.json` (user
23243
+ * scope), both in the Claude-Code shape: `{ "<Event>": [{ "matcher"?:
23244
+ * "<regex>", "hooks": [{ "type": "command" | "prompt", ..., "timeout"?:
23245
+ * <seconds>, "enabled"?: <boolean> }] }] }`. The project file also holds
23246
+ * settings rulesync does not own, so generation merges the `hooks` key into
23247
+ * either file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
23248
+ *
23249
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility
23250
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/settings
23251
+ */
23252
+ var CortexcodeHooks = class CortexcodeHooks extends ToolHooks {
23253
+ constructor(params) {
23254
+ super({
23255
+ ...params,
23256
+ fileContent: params.fileContent ?? "{}"
23257
+ });
23258
+ }
23259
+ isDeletable() {
23260
+ return false;
23261
+ }
23262
+ static getSettablePaths({ global = false } = {}) {
23263
+ return global ? {
23264
+ relativeDirPath: CORTEXCODE_GLOBAL_DIR_PATH,
23265
+ relativeFilePath: CORTEXCODE_GLOBAL_HOOKS_FILE_NAME
23266
+ } : {
23267
+ relativeDirPath: CORTEXCODE_DIR,
23268
+ relativeFilePath: CORTEXCODE_SETTINGS_FILE_NAME
23269
+ };
23270
+ }
23271
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
23272
+ const paths = CortexcodeHooks.getSettablePaths({ global });
23273
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
23274
+ return new CortexcodeHooks({
23275
+ outputRoot,
23276
+ relativeDirPath: paths.relativeDirPath,
23277
+ relativeFilePath: paths.relativeFilePath,
23278
+ fileContent,
23279
+ validate
23280
+ });
23281
+ }
23282
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
23283
+ const paths = CortexcodeHooks.getSettablePaths({ global });
23284
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
23285
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
23286
+ const config = rulesyncHooks.getJson();
23287
+ const hooks = canonicalToToolHooks({
23288
+ config,
23289
+ toolOverrideHooks: config.cortexcode?.hooks,
23290
+ converterConfig: CORTEXCODE_CONVERTER_CONFIG,
23291
+ logger
23292
+ });
23293
+ const fileContent = applySharedConfigPatch({
23294
+ fileKey: sharedConfigFileKey(paths),
23295
+ feature: "hooks",
23296
+ existingContent,
23297
+ patch: { hooks },
23298
+ filePath,
23299
+ logger
23300
+ });
23301
+ return new CortexcodeHooks({
23302
+ outputRoot,
23303
+ relativeDirPath: paths.relativeDirPath,
23304
+ relativeFilePath: paths.relativeFilePath,
23305
+ fileContent,
23306
+ validate
23307
+ });
23308
+ }
23309
+ toRulesyncHooks({ logger } = {}) {
23310
+ const configPath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
23311
+ let settings;
23312
+ try {
23313
+ settings = parseCortexcodeSettings(this.getFileContent(), configPath);
23314
+ } catch (error) {
23315
+ throw new Error(`Failed to parse Cortex Code hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
23316
+ }
23317
+ const hooks = toolHooksToCanonical({
23318
+ logger,
23319
+ hooks: settings.hooks,
23320
+ converterConfig: CORTEXCODE_CONVERTER_CONFIG
23321
+ });
23322
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
23323
+ hooks,
23324
+ overrideKey: "cortexcode"
23325
+ }), null, 2) });
23326
+ }
23327
+ validate() {
23328
+ return {
23329
+ success: true,
23330
+ error: null
23331
+ };
23332
+ }
23333
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
23334
+ return new CortexcodeHooks({
23335
+ outputRoot,
23336
+ relativeDirPath,
23337
+ relativeFilePath,
23338
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
23339
+ validate: false
23340
+ });
23341
+ }
23342
+ };
23343
+ //#endregion
22521
23344
  //#region src/features/hooks/cursor-hooks.ts
22522
23345
  var CursorHooks = class CursorHooks extends ToolHooks {
22523
23346
  constructor(params) {
@@ -25745,6 +26568,270 @@ var ReasonixHooks = class ReasonixHooks extends ToolHooks {
25745
26568
  }
25746
26569
  };
25747
26570
  //#endregion
26571
+ //#region src/features/hooks/tabnine-hooks.ts
26572
+ /**
26573
+ * Environment block safe to hand Tabnine for a hook. A tool rebuilds each
26574
+ * entry into `KEY=VALUE` for the spawned process, so a key holding `=`, a
26575
+ * control character or nothing at all names a different variable than it
26576
+ * appears to; such entries are dropped in both directions (with a warning on
26577
+ * export, where the value came from an authored `.rulesync/hooks.*`).
26578
+ */
26579
+ function sanitizeEnv({ env, warn }) {
26580
+ if (env === null || env === void 0 || typeof env !== "object" || Array.isArray(env)) return;
26581
+ const result = {};
26582
+ for (const [key, value] of Object.entries(env)) {
26583
+ const unsafeKey = key === "" || key.includes("=") || CONTROL_CHARS.some((char) => key.includes(char));
26584
+ const unsafeValue = typeof value !== "string" || CONTROL_CHARS.some((char) => value.includes(char));
26585
+ if (unsafeKey || unsafeValue) {
26586
+ warn?.(`Tabnine CLI hook env entry ${JSON.stringify(key)} is not a safe KEY=VALUE pair; skipping it.`);
26587
+ continue;
26588
+ }
26589
+ result[key] = value;
26590
+ }
26591
+ return Object.keys(result).length > 0 ? result : void 0;
26592
+ }
26593
+ /**
26594
+ * Build one Tabnine hook configuration from a canonical command definition.
26595
+ * Canonical `timeout` is seconds (docs/reference/file-formats.md); Tabnine's is
26596
+ * milliseconds.
26597
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks/configuration
26598
+ */
26599
+ function canonicalDefToTabnineHook({ def, warn }) {
26600
+ return {
26601
+ type: "command",
26602
+ ...compact({
26603
+ command: def.command,
26604
+ name: def.name,
26605
+ description: def.description,
26606
+ timeout: typeof def.timeout === "number" && Number.isFinite(def.timeout) ? Math.round(def.timeout * 1e3) : void 0,
26607
+ env: sanitizeEnv({
26608
+ env: def.env,
26609
+ warn
26610
+ })
26611
+ })
26612
+ };
26613
+ }
26614
+ /**
26615
+ * Convert the canonical hooks config to Tabnine's `hooks` map: PascalCase
26616
+ * event names, matcher groups with an optional `sequential` flag, and
26617
+ * command-only hook entries. Tabnine treats an omitted `matcher` as match-all
26618
+ * and compiles tool-event matchers as regular expressions, so the canonical
26619
+ * catch-all `"*"` (not a valid regex) is emitted as no matcher. Commands are
26620
+ * passed through verbatim; Tabnine expands `$TABNINE_PROJECT_DIR` itself.
26621
+ */
26622
+ function canonicalToTabnineHooks({ config, logger }) {
26623
+ const warn = (message) => logger?.warn(message);
26624
+ const supported = new Set(TABNINE_HOOK_EVENTS);
26625
+ const sharedHooks = {};
26626
+ for (const [event, defs] of Object.entries(config.hooks)) if (supported.has(event)) sharedHooks[event] = defs;
26627
+ const effectiveHooks = {
26628
+ ...sharedHooks,
26629
+ ...config.tabnine?.hooks
26630
+ };
26631
+ const tabnine = {};
26632
+ for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
26633
+ const tabnineEventName = lookupOwn({
26634
+ record: CANONICAL_TO_TABNINE_EVENT_NAMES,
26635
+ key: eventName
26636
+ }) ?? eventName;
26637
+ const byMatcher = /* @__PURE__ */ new Map();
26638
+ for (const def of definitions) {
26639
+ if ((def.type ?? "command") !== "command") continue;
26640
+ if (typeof def.command !== "string" || def.command === "") {
26641
+ warn(`Tabnine CLI hook on '${eventName}' has no 'command'; Tabnine would discard it, so it is skipped.`);
26642
+ continue;
26643
+ }
26644
+ const key = def.matcher === void 0 || def.matcher === "*" ? "" : def.matcher;
26645
+ const list = byMatcher.get(key);
26646
+ if (list) list.push(def);
26647
+ else byMatcher.set(key, [def]);
26648
+ }
26649
+ const entries = [];
26650
+ for (const [matcherKey, defs] of byMatcher) {
26651
+ const hooks = defs.map((def) => canonicalDefToTabnineHook({
26652
+ def,
26653
+ warn
26654
+ }));
26655
+ const sequential = defs.some((def) => def.sequential === true);
26656
+ const group = matcherKey ? {
26657
+ matcher: matcherKey,
26658
+ hooks
26659
+ } : { hooks };
26660
+ if (sequential) group.sequential = true;
26661
+ entries.push(group);
26662
+ }
26663
+ if (entries.length > 0) tabnine[tabnineEventName] = entries;
26664
+ }
26665
+ return tabnine;
26666
+ }
26667
+ /**
26668
+ * Tabnine hook configuration as stored in each matcher group's `hooks` array.
26669
+ * `z.looseObject` keeps fields added by future Tabnine releases.
26670
+ */
26671
+ const TabnineHookEntrySchema = z.looseObject({
26672
+ type: z.optional(z.string()),
26673
+ command: z.optional(z.string()),
26674
+ name: z.optional(z.string()),
26675
+ description: z.optional(z.string()),
26676
+ timeout: z.optional(z.number()),
26677
+ env: z.optional(z.record(z.string(), z.string()))
26678
+ });
26679
+ const TabnineMatcherEntrySchema = z.looseObject({
26680
+ matcher: z.optional(z.string()),
26681
+ sequential: z.optional(z.boolean()),
26682
+ hooks: z.optional(z.array(TabnineHookEntrySchema))
26683
+ });
26684
+ function tabnineMatcherEntryToCanonical(entry) {
26685
+ const sequential = entry.sequential === true;
26686
+ const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
26687
+ const defs = [];
26688
+ for (const hook of entry.hooks ?? []) {
26689
+ if (hook.type !== "command" || typeof hook.command !== "string" || hook.command === "") continue;
26690
+ defs.push({
26691
+ type: "command",
26692
+ ...compact({
26693
+ command: hook.command,
26694
+ name: hook.name,
26695
+ description: hook.description,
26696
+ timeout: typeof hook.timeout === "number" && Number.isFinite(hook.timeout) ? hook.timeout / 1e3 : void 0,
26697
+ env: sanitizeEnv({ env: hook.env }),
26698
+ sequential: sequential ? true : void 0,
26699
+ matcher
26700
+ })
26701
+ });
26702
+ }
26703
+ return defs;
26704
+ }
26705
+ /**
26706
+ * Extract the `hooks` map of a Tabnine settings file into canonical form.
26707
+ */
26708
+ function tabnineHooksToCanonical(tabnineHooks) {
26709
+ if (tabnineHooks === null || tabnineHooks === void 0 || typeof tabnineHooks !== "object") return {};
26710
+ const canonical = {};
26711
+ for (const [tabnineEventName, matcherEntries] of Object.entries(tabnineHooks)) {
26712
+ const eventName = lookupOwn({
26713
+ record: TABNINE_TO_CANONICAL_EVENT_NAMES,
26714
+ key: tabnineEventName
26715
+ }) ?? tabnineEventName;
26716
+ if (!Array.isArray(matcherEntries)) continue;
26717
+ const defs = [];
26718
+ for (const rawEntry of matcherEntries) {
26719
+ const parseResult = TabnineMatcherEntrySchema.safeParse(rawEntry);
26720
+ if (!parseResult.success) continue;
26721
+ defs.push(...tabnineMatcherEntryToCanonical(parseResult.data));
26722
+ }
26723
+ if (defs.length > 0) canonical[eventName] = defs;
26724
+ }
26725
+ return canonical;
26726
+ }
26727
+ /**
26728
+ * Fail closed on an unparseable settings root rather than replacing the
26729
+ * user's Tabnine settings with generated output.
26730
+ */
26731
+ function parseTabnineSettings(fileContent, filePath) {
26732
+ return parseSharedConfig({
26733
+ format: "json",
26734
+ fileContent,
26735
+ filePath,
26736
+ invalidRootPolicy: "error"
26737
+ });
26738
+ }
26739
+ /**
26740
+ * Tabnine CLI hooks.
26741
+ *
26742
+ * Hooks live under the top-level `hooks` key of Tabnine's settings file —
26743
+ * `<project>/.tabnine/agent/settings.json` (project scope) and
26744
+ * `~/.tabnine/agent/settings.json` (user scope). The file also carries
26745
+ * `mcpServers`, `tools` and other user-managed settings, so generation merges
26746
+ * the `hooks` key into it (see `SHARED_CONFIG_OWNERSHIP`) instead of
26747
+ * overwriting the file. The sibling `hooksConfig` block (master toggle,
26748
+ * disabled list, notifications) is left to the user.
26749
+ *
26750
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks
26751
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks/configuration
26752
+ */
26753
+ var TabnineHooks = class TabnineHooks extends ToolHooks {
26754
+ constructor(params) {
26755
+ super({
26756
+ ...params,
26757
+ fileContent: params.fileContent ?? "{}"
26758
+ });
26759
+ }
26760
+ isDeletable() {
26761
+ return false;
26762
+ }
26763
+ static getSettablePaths(_options = {}) {
26764
+ return {
26765
+ relativeDirPath: TABNINE_AGENT_DIR_PATH,
26766
+ relativeFilePath: TABNINE_SETTINGS_FILE_NAME
26767
+ };
26768
+ }
26769
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
26770
+ const paths = TabnineHooks.getSettablePaths({ global });
26771
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
26772
+ return new TabnineHooks({
26773
+ outputRoot,
26774
+ relativeDirPath: paths.relativeDirPath,
26775
+ relativeFilePath: paths.relativeFilePath,
26776
+ fileContent,
26777
+ validate
26778
+ });
26779
+ }
26780
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
26781
+ const paths = TabnineHooks.getSettablePaths({ global });
26782
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
26783
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
26784
+ const hooks = canonicalToTabnineHooks({
26785
+ config: rulesyncHooks.getJson(),
26786
+ logger
26787
+ });
26788
+ const fileContent = applySharedConfigPatch({
26789
+ fileKey: sharedConfigFileKey(paths),
26790
+ feature: "hooks",
26791
+ existingContent,
26792
+ patch: { hooks },
26793
+ filePath,
26794
+ logger
26795
+ });
26796
+ return new TabnineHooks({
26797
+ outputRoot,
26798
+ relativeDirPath: paths.relativeDirPath,
26799
+ relativeFilePath: paths.relativeFilePath,
26800
+ fileContent,
26801
+ validate
26802
+ });
26803
+ }
26804
+ toRulesyncHooks() {
26805
+ const configPath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
26806
+ let settings;
26807
+ try {
26808
+ settings = parseTabnineSettings(this.getFileContent(), configPath);
26809
+ } catch (error) {
26810
+ throw new Error(`Failed to parse Tabnine CLI hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
26811
+ }
26812
+ const hooks = tabnineHooksToCanonical(settings.hooks);
26813
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
26814
+ hooks,
26815
+ overrideKey: "tabnine"
26816
+ }), null, 2) });
26817
+ }
26818
+ validate() {
26819
+ return {
26820
+ success: true,
26821
+ error: null
26822
+ };
26823
+ }
26824
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
26825
+ return new TabnineHooks({
26826
+ outputRoot,
26827
+ relativeDirPath,
26828
+ relativeFilePath,
26829
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
26830
+ validate: false
26831
+ });
26832
+ }
26833
+ };
26834
+ //#endregion
25748
26835
  //#region src/features/hooks/vibe-hooks.ts
25749
26836
  const VIBE_DIR = ".vibe";
25750
26837
  const VIBE_HOOKS_FILE_NAME = "hooks.toml";
@@ -26504,6 +27591,40 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
26504
27591
  supportedHookTypes: ["command"],
26505
27592
  supportsMatcher: true
26506
27593
  }],
27594
+ ["bob", {
27595
+ class: BobHooks,
27596
+ meta: {
27597
+ supportsProject: true,
27598
+ supportsGlobal: true,
27599
+ supportsImport: true
27600
+ },
27601
+ supportedEvents: BOB_HOOK_EVENTS,
27602
+ supportedHookTypes: ["command"],
27603
+ supportsMatcher: true,
27604
+ matcherEvents: ["preToolUse", "postToolUse"]
27605
+ }],
27606
+ ["cortexcode", {
27607
+ class: CortexcodeHooks,
27608
+ meta: {
27609
+ supportsProject: true,
27610
+ supportsGlobal: true,
27611
+ supportsImport: true
27612
+ },
27613
+ supportedEvents: CORTEXCODE_HOOK_EVENTS,
27614
+ supportedHookTypes: ["command", "prompt"],
27615
+ supportsMatcher: true,
27616
+ matcherEvents: [
27617
+ "preToolUse",
27618
+ "postToolUse",
27619
+ "permissionRequest",
27620
+ "sessionStart",
27621
+ "sessionEnd",
27622
+ "preCompact",
27623
+ "subagentStop",
27624
+ "notification",
27625
+ "setup"
27626
+ ]
27627
+ }],
26507
27628
  ["junie", {
26508
27629
  class: JunieHooks,
26509
27630
  meta: {
@@ -26515,6 +27636,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
26515
27636
  supportedHookTypes: ["command"],
26516
27637
  supportsMatcher: true
26517
27638
  }],
27639
+ ["tabnine", {
27640
+ class: TabnineHooks,
27641
+ meta: {
27642
+ supportsProject: true,
27643
+ supportsGlobal: true,
27644
+ supportsImport: true
27645
+ },
27646
+ supportedEvents: TABNINE_HOOK_EVENTS,
27647
+ supportedHookTypes: ["command"],
27648
+ supportsMatcher: true
27649
+ }],
26518
27650
  ["vibe", {
26519
27651
  class: VibeHooks,
26520
27652
  meta: {
@@ -26961,6 +28093,58 @@ var AugmentcodeIgnore = class AugmentcodeIgnore extends ToolIgnore {
26961
28093
  }
26962
28094
  };
26963
28095
  //#endregion
28096
+ //#region src/features/ignore/bob-ignore.ts
28097
+ /**
28098
+ * BobIgnore represents the `.bobignore` file of IBM Bob.
28099
+ *
28100
+ * - File location: workspace root only (`.bobignore`); no user-scope variant
28101
+ * is documented, so the feature is project-only.
28102
+ * - Syntax: the same as `.gitignore`.
28103
+ * - Matching files are hidden from Bob's file reads and listings.
28104
+ *
28105
+ * @see https://bob.ibm.com/docs/ide/configuration/bobignore
28106
+ */
28107
+ var BobIgnore = class BobIgnore extends ToolIgnore {
28108
+ static getSettablePaths() {
28109
+ return {
28110
+ relativeDirPath: ".",
28111
+ relativeFilePath: BOB_IGNORE_FILE_NAME
28112
+ };
28113
+ }
28114
+ toRulesyncIgnore() {
28115
+ return this.toRulesyncIgnoreDefault();
28116
+ }
28117
+ static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
28118
+ const paths = this.getSettablePaths();
28119
+ return new BobIgnore({
28120
+ outputRoot,
28121
+ relativeDirPath: paths.relativeDirPath,
28122
+ relativeFilePath: paths.relativeFilePath,
28123
+ fileContent: rulesyncIgnore.getFileContent()
28124
+ });
28125
+ }
28126
+ static async fromFile({ outputRoot = process.cwd(), validate = true }) {
28127
+ const paths = this.getSettablePaths();
28128
+ const fileContent = await readFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
28129
+ return new BobIgnore({
28130
+ outputRoot,
28131
+ relativeDirPath: paths.relativeDirPath,
28132
+ relativeFilePath: paths.relativeFilePath,
28133
+ fileContent,
28134
+ validate
28135
+ });
28136
+ }
28137
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
28138
+ return new BobIgnore({
28139
+ outputRoot,
28140
+ relativeDirPath,
28141
+ relativeFilePath,
28142
+ fileContent: "",
28143
+ validate: false
28144
+ });
28145
+ }
28146
+ };
28147
+ //#endregion
26964
28148
  //#region src/features/ignore/claudecode-ignore.ts
26965
28149
  const DEFAULT_FILE_MODE = "shared";
26966
28150
  /**
@@ -27990,6 +29174,60 @@ var RooIgnore = class RooIgnore extends ToolIgnore {
27990
29174
  }
27991
29175
  };
27992
29176
  //#endregion
29177
+ //#region src/features/ignore/tabnine-ignore.ts
29178
+ /**
29179
+ * TabnineIgnore represents the `.tabnineignore` file of Tabnine CLI.
29180
+ *
29181
+ * - File location: project root only (`.tabnineignore`); no user-scope
29182
+ * variant is documented, so the feature is project-only.
29183
+ * - Syntax: the same as `.gitignore`.
29184
+ * - Matching files are excluded from the CLI's file searches and directory
29185
+ * listings while `context.fileFiltering.respectGeminiIgnore` (the default)
29186
+ * is enabled.
29187
+ *
29188
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference
29189
+ */
29190
+ var TabnineIgnore = class TabnineIgnore extends ToolIgnore {
29191
+ static getSettablePaths() {
29192
+ return {
29193
+ relativeDirPath: ".",
29194
+ relativeFilePath: TABNINE_IGNORE_FILE_NAME
29195
+ };
29196
+ }
29197
+ toRulesyncIgnore() {
29198
+ return this.toRulesyncIgnoreDefault();
29199
+ }
29200
+ static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
29201
+ const paths = this.getSettablePaths();
29202
+ return new TabnineIgnore({
29203
+ outputRoot,
29204
+ relativeDirPath: paths.relativeDirPath,
29205
+ relativeFilePath: paths.relativeFilePath,
29206
+ fileContent: rulesyncIgnore.getFileContent()
29207
+ });
29208
+ }
29209
+ static async fromFile({ outputRoot = process.cwd(), validate = true }) {
29210
+ const paths = this.getSettablePaths();
29211
+ const fileContent = await readFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
29212
+ return new TabnineIgnore({
29213
+ outputRoot,
29214
+ relativeDirPath: paths.relativeDirPath,
29215
+ relativeFilePath: paths.relativeFilePath,
29216
+ fileContent,
29217
+ validate
29218
+ });
29219
+ }
29220
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
29221
+ return new TabnineIgnore({
29222
+ outputRoot,
29223
+ relativeDirPath,
29224
+ relativeFilePath,
29225
+ fileContent: "",
29226
+ validate: false
29227
+ });
29228
+ }
29229
+ };
29230
+ //#endregion
27993
29231
  //#region src/features/ignore/vibe-ignore.ts
27994
29232
  var VibeIgnore = class VibeIgnore extends ToolIgnore {
27995
29233
  static getSettablePaths() {
@@ -28195,6 +29433,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
28195
29433
  ["aiassistant", { class: AiassistantIgnore }],
28196
29434
  ["antigravity-cli", { class: AntigravityCliIgnore }],
28197
29435
  ["augmentcode", { class: AugmentcodeIgnore }],
29436
+ ["bob", { class: BobIgnore }],
28198
29437
  ["claudecode", { class: ClaudecodeIgnore }],
28199
29438
  ["claudecode-legacy", { class: ClaudecodeIgnore }],
28200
29439
  ["cline", { class: ClineIgnore }],
@@ -28211,6 +29450,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
28211
29450
  ["roo", { class: RooIgnore }],
28212
29451
  ["zoocode", { class: RooIgnore }],
28213
29452
  ["devin", { class: DevinIgnore }],
29453
+ ["tabnine", { class: TabnineIgnore }],
28214
29454
  ["vibe", { class: VibeIgnore }],
28215
29455
  ["warp", { class: WarpIgnore }],
28216
29456
  ["zed", { class: ZedIgnore }]
@@ -28945,6 +30185,343 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
28945
30185
  }
28946
30186
  };
28947
30187
  //#endregion
30188
+ //#region src/features/mcp/mcp-transport.ts
30189
+ /**
30190
+ * A server that names no way to be reached at all. It is what a Kilo bare
30191
+ * `{"enabled": <bool>}` entry imports as — a switch for a server another config
30192
+ * layer defines, which only Kilo's config spells this way. Every other tool's
30193
+ * config defines the servers it lists, so there the entry has no equivalent and
30194
+ * must be skipped rather than written as a server with an empty command.
30195
+ */
30196
+ function declaresNoTransport(serverConfig) {
30197
+ return serverConfig.type === void 0 && serverConfig.transport === void 0 && serverConfig.command === void 0 && serverConfig.url === void 0 && serverConfig.httpUrl === void 0;
30198
+ }
30199
+ const REMOTE_TRANSPORTS = /* @__PURE__ */ new Set([
30200
+ "sse",
30201
+ "http",
30202
+ "streamable-http",
30203
+ "ws"
30204
+ ]);
30205
+ /**
30206
+ * Whether the server speaks over the network rather than over a spawned
30207
+ * process. `httpUrl`, the Claude-specific alias several adapters accept, counts
30208
+ * as much as `url` does, and so does `transport`, the alias `type` has
30209
+ * everywhere in the canonical config.
30210
+ */
30211
+ function isRemoteMcpServer(serverConfig) {
30212
+ return REMOTE_TRANSPORTS.has(serverConfig.type ?? "") || REMOTE_TRANSPORTS.has(serverConfig.transport ?? "") || serverConfig.url !== void 0 || serverConfig.httpUrl !== void 0;
30213
+ }
30214
+ /** The URL a remote server is reached at, or `undefined` if it states none. */
30215
+ function resolveRemoteMcpUrl(serverConfig) {
30216
+ return serverConfig.url || serverConfig.httpUrl || void 0;
30217
+ }
30218
+ /** The `command` array a `local` server is spawned with, `args` merged in. */
30219
+ function resolveLocalMcpCommand(serverConfig) {
30220
+ const commandArray = [];
30221
+ if (serverConfig.command) {
30222
+ if (Array.isArray(serverConfig.command)) commandArray.push(...serverConfig.command);
30223
+ else commandArray.push(serverConfig.command);
30224
+ }
30225
+ if (serverConfig.args) commandArray.push(...serverConfig.args);
30226
+ return commandArray;
30227
+ }
30228
+ /**
30229
+ * A server that names a transport but carries nothing to reach it — a `type`
30230
+ * with no `command`, an `http` with no `url` — has no form in a config that
30231
+ * spells the transport out. Writing `{type: "local", command: []}` for it would
30232
+ * give the tool a server it cannot start and the importer a file it cannot read
30233
+ * back, so it is skipped out loud instead.
30234
+ */
30235
+ function warnAndSkipMcpServer({ toolName, serverName, reason, logger }) {
30236
+ logger?.warn(`${toolName} MCP: skipping "${serverName}" because it declares ${reason}.`);
30237
+ return null;
30238
+ }
30239
+ /**
30240
+ * The `tools` map Kilo and OpenCode keep beside `mcp` is keyed `<server>_<tool>`
30241
+ * and reaches servers `mcp` does not list — ones the global config or a
30242
+ * marketplace defines — so entries naming no listed
30243
+ * server are filters that would be dropped by a walk over `mcp` alone, and
30244
+ * deleted from the file on the next generate. They come back as transport-less
30245
+ * servers carrying nothing but the filters.
30246
+ *
30247
+ * The split is at the first `_`, which is where the write side joined them. A
30248
+ * server whose own name contains one is reconstructed under a shorter name, but
30249
+ * the key it is written back as is character-for-character the one that was
30250
+ * read, so Kilo sees exactly what it saw before.
30251
+ */
30252
+ function orphanMcpToolFiltersToRulesync(servers, tools) {
30253
+ const orphans = {};
30254
+ for (const [toolName, enabled] of Object.entries(tools ?? {})) {
30255
+ if (Object.keys(servers).some((serverName) => toolName.startsWith(`${serverName}_`))) continue;
30256
+ const separator = toolName.indexOf("_");
30257
+ if (separator <= 0 || separator === toolName.length - 1) continue;
30258
+ const serverName = toolName.slice(0, separator);
30259
+ const toolSuffix = toolName.slice(separator + 1);
30260
+ if (!Object.hasOwn(orphans, serverName)) orphans[serverName] = {};
30261
+ const server = orphans[serverName];
30262
+ if (enabled) server.enabledTools = [...server.enabledTools ?? [], toolSuffix];
30263
+ else server.disabledTools = [...server.disabledTools ?? [], toolSuffix];
30264
+ }
30265
+ return orphans;
30266
+ }
30267
+ /**
30268
+ * Split imported servers into the ones every tool can use and the ones only the
30269
+ * importing tool can. A server with no transport is a Kilo/OpenCode idea — a
30270
+ * switch for a server another config layer defines, or a filter for one — and
30271
+ * nothing else knows what to do with it: written to `.mcp.json` and the rest,
30272
+ * it is an entry no tool can start. It goes into the tool-scoped
30273
+ * `{tool}.mcpServers` block instead, which only that tool reads.
30274
+ */
30275
+ function splitMcpServersByTransport(servers) {
30276
+ const shared = {};
30277
+ const toolOnly = {};
30278
+ for (const [serverName, serverConfig] of Object.entries(servers)) if (declaresNoTransport(serverConfig)) toolOnly[serverName] = serverConfig;
30279
+ else shared[serverName] = serverConfig;
30280
+ return {
30281
+ shared,
30282
+ toolOnly
30283
+ };
30284
+ }
30285
+ //#endregion
30286
+ //#region src/features/mcp/bob-mcp.ts
30287
+ /**
30288
+ * Parse a Bob MCP file, failing closed on malformed JSON or a non-object root
30289
+ * (`null`, an array, a scalar) rather than spreading whatever came back into
30290
+ * the regenerated file.
30291
+ */
30292
+ function parseBobMcpConfig({ fileContent, relativePath }) {
30293
+ let parsed;
30294
+ try {
30295
+ parsed = JSON.parse(fileContent);
30296
+ } catch (error) {
30297
+ throw new Error(`Failed to parse Bob MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
30298
+ }
30299
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Bob MCP config at ${relativePath}: expected a JSON object at the root`);
30300
+ return parsed;
30301
+ }
30302
+ /** The `type` Bob IDE writes for a streamable HTTP server. */
30303
+ const BOB_STREAMABLE_HTTP_TYPE = "streamable-http";
30304
+ /**
30305
+ * The remote transport Bob IDE reads a server as, spelled the way it writes
30306
+ * it: `streamable-http` needs an explicit `type`, while SSE (legacy) is a bare
30307
+ * `url` with no `type`. `http` is the canonical rulesync alias for streamable
30308
+ * HTTP and a bare `url` defaults to it, streamable HTTP being the current MCP
30309
+ * transport. A `ws(s)://` URL or any other stated transport is something Bob
30310
+ * cannot reach, so `undefined` tells the caller to skip the server.
30311
+ * @see https://bob.ibm.com/docs/ide/configuration/mcp/mcp-in-bob
30312
+ */
30313
+ function asBobRemoteType(stated, url) {
30314
+ if (stated === "sse") return "sse";
30315
+ if (stated === "http" || stated === BOB_STREAMABLE_HTTP_TYPE) return BOB_STREAMABLE_HTTP_TYPE;
30316
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : BOB_STREAMABLE_HTTP_TYPE;
30317
+ }
30318
+ /**
30319
+ * Convert the canonical server map to the shape Bob IDE documents for
30320
+ * `.bob/mcp.json`: a stdio server carries `command` (plus `args`), a
30321
+ * streamable HTTP server carries `type: "streamable-http"` and `url`, and an
30322
+ * SSE server carries a bare `url`. The canonical `transport` alias and the
30323
+ * Claude-style `httpUrl` alias are folded into `type`/`url`; `env`, `cwd`,
30324
+ * `headers`, `timeout`, `alwaysAllow` and `disabled` pass through unchanged,
30325
+ * as Bob documents all of them.
30326
+ *
30327
+ * Bob Shell documents the same file with an `httpURL` key instead of
30328
+ * `type` + `url` for streamable HTTP. rulesync writes the IDE spelling (the two
30329
+ * products share the project file, and the IDE is the one whose global file
30330
+ * rulesync targets) and accepts the Shell spelling on import.
30331
+ *
30332
+ * A server Bob cannot start or reach — no transport at all, a remote transport
30333
+ * without a URL, a WebSocket URL, or a stdio entry without a command — is
30334
+ * skipped with a warning rather than written in a broken form.
30335
+ * @see https://bob.ibm.com/docs/ide/configuration/mcp/mcp-in-bob
30336
+ * @see https://bob.ibm.com/docs/shell/configuration/mcp/mcp-bobshell
30337
+ */
30338
+ function convertToBobFormat(mcpServers, logger) {
30339
+ const result = {};
30340
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
30341
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
30342
+ if (declaresNoTransport(serverConfig)) {
30343
+ warnAndSkipMcpServer({
30344
+ toolName: "Bob",
30345
+ serverName,
30346
+ reason: "no transport",
30347
+ logger
30348
+ });
30349
+ continue;
30350
+ }
30351
+ const { type, transport, url: _url, httpUrl: _httpUrl, command: _command, args: _args, ...rest } = serverConfig;
30352
+ const converted = {};
30353
+ if (isRemoteMcpServer(serverConfig)) {
30354
+ const url = resolveRemoteMcpUrl(serverConfig);
30355
+ if (!url) {
30356
+ warnAndSkipMcpServer({
30357
+ toolName: "Bob",
30358
+ serverName,
30359
+ reason: "a remote transport without a url",
30360
+ logger
30361
+ });
30362
+ continue;
30363
+ }
30364
+ const stated = type ?? transport;
30365
+ const remoteType = asBobRemoteType(stated, url);
30366
+ if (remoteType === void 0) {
30367
+ warnAndSkipMcpServer({
30368
+ toolName: "Bob",
30369
+ serverName,
30370
+ reason: stated === void 0 ? "a WebSocket url, which Bob's remote transports (streamable-http and sse) cannot reach" : `the "${stated}" transport, which Bob does not offer for remote servers (only streamable-http and sse)`,
30371
+ logger
30372
+ });
30373
+ continue;
30374
+ }
30375
+ if (remoteType === BOB_STREAMABLE_HTTP_TYPE) converted.type = BOB_STREAMABLE_HTTP_TYPE;
30376
+ converted.url = url;
30377
+ } else {
30378
+ const [command, ...args] = resolveLocalMcpCommand(serverConfig);
30379
+ if (!command) {
30380
+ warnAndSkipMcpServer({
30381
+ toolName: "Bob",
30382
+ serverName,
30383
+ reason: "a stdio transport without a command",
30384
+ logger
30385
+ });
30386
+ continue;
30387
+ }
30388
+ converted.command = command;
30389
+ if (args.length > 0) converted.args = args;
30390
+ }
30391
+ for (const [key, value] of Object.entries(rest)) {
30392
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
30393
+ converted[key] = (key === "env" || key === "headers") && isRecord$1(value) ? omitPrototypePollutionKeys(value) : value;
30394
+ }
30395
+ result[serverName] = converted;
30396
+ }
30397
+ return result;
30398
+ }
30399
+ /**
30400
+ * Convert Bob's server map back to the canonical shape. The IDE spelling
30401
+ * (`type: "streamable-http"` + `url`) is already canonical and passes through;
30402
+ * the Bob Shell spelling `httpURL` becomes `url` with `type: "http"`; a bare
30403
+ * `url` is an SSE server in Bob, so it gains `type: "sse"` to keep that reading
30404
+ * on the next generate.
30405
+ */
30406
+ function convertFromBobFormat(mcpServers) {
30407
+ if (!isMcpServers(mcpServers)) return {};
30408
+ const result = {};
30409
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
30410
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
30411
+ const converted = {};
30412
+ let httpURL;
30413
+ for (const [key, value] of Object.entries(serverConfig)) {
30414
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
30415
+ if (key === "httpURL") {
30416
+ if (typeof value === "string") httpURL = value;
30417
+ continue;
30418
+ }
30419
+ converted[key] = value;
30420
+ }
30421
+ if (httpURL !== void 0) {
30422
+ converted.url = httpURL;
30423
+ converted.type = "http";
30424
+ } else if (typeof converted.url === "string" && converted.type === void 0) converted.type = "sse";
30425
+ result[serverName] = converted;
30426
+ }
30427
+ return result;
30428
+ }
30429
+ /**
30430
+ * IBM Bob MCP configuration.
30431
+ *
30432
+ * Bob IDE reads `mcpServers` from `<project>/.bob/mcp.json` (project scope)
30433
+ * and `~/.bob/mcp.json` (user scope); the project entry wins when both define
30434
+ * the same server name. Both files are dedicated to MCP. The project one is
30435
+ * deletable; the user one is created and edited by Bob IDE's own MCP settings
30436
+ * UI, so `--delete` leaves it in place rather than removing a file the user
30437
+ * did not create through rulesync. (Bob Shell reads its user-scoped servers
30438
+ * from `~/.bob/mcp_settings.json` instead, which rulesync does not write.)
30439
+ *
30440
+ * @see https://bob.ibm.com/docs/ide/configuration/mcp/mcp-in-bob
30441
+ * @see https://bob.ibm.com/docs/shell/configuration/mcp/mcp-bobshell
30442
+ */
30443
+ var BobMcp = class BobMcp extends ToolMcp {
30444
+ json;
30445
+ constructor(params) {
30446
+ super(params);
30447
+ this.json = this.fileContent === void 0 ? {} : parseBobMcpConfig({
30448
+ fileContent: this.fileContent,
30449
+ relativePath: join(this.relativeDirPath, this.relativeFilePath)
30450
+ });
30451
+ }
30452
+ getJson() {
30453
+ return this.json;
30454
+ }
30455
+ isDeletable() {
30456
+ return !this.global;
30457
+ }
30458
+ static getSettablePaths(_options = {}) {
30459
+ return {
30460
+ relativeDirPath: BOB_DIR,
30461
+ relativeFilePath: BOB_MCP_FILE_NAME
30462
+ };
30463
+ }
30464
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
30465
+ const paths = this.getSettablePaths({ global });
30466
+ const json = parseBobMcpConfig({
30467
+ fileContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
30468
+ relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
30469
+ });
30470
+ const newJson = {
30471
+ ...json,
30472
+ mcpServers: json.mcpServers ?? {}
30473
+ };
30474
+ return new BobMcp({
30475
+ outputRoot,
30476
+ relativeDirPath: paths.relativeDirPath,
30477
+ relativeFilePath: paths.relativeFilePath,
30478
+ fileContent: JSON.stringify(newJson, null, 2),
30479
+ validate,
30480
+ global
30481
+ });
30482
+ }
30483
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
30484
+ const paths = this.getSettablePaths({ global });
30485
+ const json = parseBobMcpConfig({
30486
+ fileContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2),
30487
+ relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
30488
+ });
30489
+ const mcpServers = convertToBobFormat(rulesyncMcp.getMcpServers(), logger);
30490
+ const bobConfig = {
30491
+ ...json,
30492
+ mcpServers
30493
+ };
30494
+ return new BobMcp({
30495
+ outputRoot,
30496
+ relativeDirPath: paths.relativeDirPath,
30497
+ relativeFilePath: paths.relativeFilePath,
30498
+ fileContent: JSON.stringify(bobConfig, null, 2),
30499
+ validate,
30500
+ global
30501
+ });
30502
+ }
30503
+ toRulesyncMcp() {
30504
+ const mcpServers = convertFromBobFormat(this.json.mcpServers);
30505
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
30506
+ }
30507
+ validate() {
30508
+ return {
30509
+ success: true,
30510
+ error: null
30511
+ };
30512
+ }
30513
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
30514
+ return new BobMcp({
30515
+ outputRoot,
30516
+ relativeDirPath,
30517
+ relativeFilePath,
30518
+ fileContent: "{}",
30519
+ validate: false,
30520
+ global
30521
+ });
30522
+ }
30523
+ };
30524
+ //#endregion
28948
30525
  //#region src/features/mcp/claudecode-mcp.ts
28949
30526
  var ClaudecodeMcp = class ClaudecodeMcp extends ToolMcp {
28950
30527
  json;
@@ -29652,104 +31229,6 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
29652
31229
  }
29653
31230
  };
29654
31231
  //#endregion
29655
- //#region src/features/mcp/mcp-transport.ts
29656
- /**
29657
- * A server that names no way to be reached at all. It is what a Kilo bare
29658
- * `{"enabled": <bool>}` entry imports as — a switch for a server another config
29659
- * layer defines, which only Kilo's config spells this way. Every other tool's
29660
- * config defines the servers it lists, so there the entry has no equivalent and
29661
- * must be skipped rather than written as a server with an empty command.
29662
- */
29663
- function declaresNoTransport(serverConfig) {
29664
- return serverConfig.type === void 0 && serverConfig.transport === void 0 && serverConfig.command === void 0 && serverConfig.url === void 0 && serverConfig.httpUrl === void 0;
29665
- }
29666
- const REMOTE_TRANSPORTS = /* @__PURE__ */ new Set([
29667
- "sse",
29668
- "http",
29669
- "streamable-http",
29670
- "ws"
29671
- ]);
29672
- /**
29673
- * Whether the server speaks over the network rather than over a spawned
29674
- * process. `httpUrl`, the Claude-specific alias several adapters accept, counts
29675
- * as much as `url` does, and so does `transport`, the alias `type` has
29676
- * everywhere in the canonical config.
29677
- */
29678
- function isRemoteMcpServer(serverConfig) {
29679
- return REMOTE_TRANSPORTS.has(serverConfig.type ?? "") || REMOTE_TRANSPORTS.has(serverConfig.transport ?? "") || serverConfig.url !== void 0 || serverConfig.httpUrl !== void 0;
29680
- }
29681
- /** The URL a remote server is reached at, or `undefined` if it states none. */
29682
- function resolveRemoteMcpUrl(serverConfig) {
29683
- return serverConfig.url || serverConfig.httpUrl || void 0;
29684
- }
29685
- /** The `command` array a `local` server is spawned with, `args` merged in. */
29686
- function resolveLocalMcpCommand(serverConfig) {
29687
- const commandArray = [];
29688
- if (serverConfig.command) {
29689
- if (Array.isArray(serverConfig.command)) commandArray.push(...serverConfig.command);
29690
- else commandArray.push(serverConfig.command);
29691
- }
29692
- if (serverConfig.args) commandArray.push(...serverConfig.args);
29693
- return commandArray;
29694
- }
29695
- /**
29696
- * A server that names a transport but carries nothing to reach it — a `type`
29697
- * with no `command`, an `http` with no `url` — has no form in a config that
29698
- * spells the transport out. Writing `{type: "local", command: []}` for it would
29699
- * give the tool a server it cannot start and the importer a file it cannot read
29700
- * back, so it is skipped out loud instead.
29701
- */
29702
- function warnAndSkipMcpServer({ toolName, serverName, reason, logger }) {
29703
- logger?.warn(`${toolName} MCP: skipping "${serverName}" because it declares ${reason}.`);
29704
- return null;
29705
- }
29706
- /**
29707
- * The `tools` map Kilo and OpenCode keep beside `mcp` is keyed `<server>_<tool>`
29708
- * and reaches servers `mcp` does not list — ones the global config or a
29709
- * marketplace defines — so entries naming no listed
29710
- * server are filters that would be dropped by a walk over `mcp` alone, and
29711
- * deleted from the file on the next generate. They come back as transport-less
29712
- * servers carrying nothing but the filters.
29713
- *
29714
- * The split is at the first `_`, which is where the write side joined them. A
29715
- * server whose own name contains one is reconstructed under a shorter name, but
29716
- * the key it is written back as is character-for-character the one that was
29717
- * read, so Kilo sees exactly what it saw before.
29718
- */
29719
- function orphanMcpToolFiltersToRulesync(servers, tools) {
29720
- const orphans = {};
29721
- for (const [toolName, enabled] of Object.entries(tools ?? {})) {
29722
- if (Object.keys(servers).some((serverName) => toolName.startsWith(`${serverName}_`))) continue;
29723
- const separator = toolName.indexOf("_");
29724
- if (separator <= 0 || separator === toolName.length - 1) continue;
29725
- const serverName = toolName.slice(0, separator);
29726
- const toolSuffix = toolName.slice(separator + 1);
29727
- if (!Object.hasOwn(orphans, serverName)) orphans[serverName] = {};
29728
- const server = orphans[serverName];
29729
- if (enabled) server.enabledTools = [...server.enabledTools ?? [], toolSuffix];
29730
- else server.disabledTools = [...server.disabledTools ?? [], toolSuffix];
29731
- }
29732
- return orphans;
29733
- }
29734
- /**
29735
- * Split imported servers into the ones every tool can use and the ones only the
29736
- * importing tool can. A server with no transport is a Kilo/OpenCode idea — a
29737
- * switch for a server another config layer defines, or a filter for one — and
29738
- * nothing else knows what to do with it: written to `.mcp.json` and the rest,
29739
- * it is an entry no tool can start. It goes into the tool-scoped
29740
- * `{tool}.mcpServers` block instead, which only that tool reads.
29741
- */
29742
- function splitMcpServersByTransport(servers) {
29743
- const shared = {};
29744
- const toolOnly = {};
29745
- for (const [serverName, serverConfig] of Object.entries(servers)) if (declaresNoTransport(serverConfig)) toolOnly[serverName] = serverConfig;
29746
- else shared[serverName] = serverConfig;
29747
- return {
29748
- shared,
29749
- toolOnly
29750
- };
29751
- }
29752
- //#endregion
29753
31232
  //#region src/features/mcp/copilotcli-mcp.ts
29754
31233
  /**
29755
31234
  * Reached over WebSocket, which Copilot CLI has no transport for. The url is
@@ -30018,6 +31497,222 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
30018
31497
  }
30019
31498
  };
30020
31499
  //#endregion
31500
+ //#region src/features/mcp/cortexcode-mcp.ts
31501
+ const CORTEXCODE_GLOBAL_ONLY_MESSAGE = "Snowflake Cortex Code MCP is global-only; use --global to sync ~/.snowflake/cortex/mcp.json";
31502
+ /**
31503
+ * Parse a Cortex Code MCP file, failing closed on malformed JSON or a
31504
+ * non-object root (`null`, an array, a scalar) rather than spreading whatever
31505
+ * came back into the regenerated file.
31506
+ */
31507
+ function parseCortexcodeMcpConfig({ fileContent, relativePath }) {
31508
+ let parsed;
31509
+ try {
31510
+ parsed = JSON.parse(fileContent);
31511
+ } catch (error) {
31512
+ throw new Error(`Failed to parse Cortex Code MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
31513
+ }
31514
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Cortex Code MCP config at ${relativePath}: expected a JSON object at the root`);
31515
+ return parsed;
31516
+ }
31517
+ /**
31518
+ * The remote transport Cortex Code reads a server as. Cortex Code documents
31519
+ * exactly three transports — `stdio`, `http` and `sse` — each spelled out in
31520
+ * an explicit `type` key. `http` is also the canonical rulesync spelling, and
31521
+ * a bare `url` defaults to it, streamable HTTP being the current MCP
31522
+ * transport. `streamable-http` is folded into `http`. A `ws(s)://` URL or any
31523
+ * other stated transport is something Cortex Code cannot reach, so
31524
+ * `undefined` tells the caller to skip the server.
31525
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility#transport-types
31526
+ */
31527
+ function asCortexcodeRemoteType(stated, url) {
31528
+ if (stated === "sse") return "sse";
31529
+ if (stated === "http" || stated === "streamable-http") return "http";
31530
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
31531
+ }
31532
+ /**
31533
+ * Convert the canonical server map to the shape Cortex Code documents for
31534
+ * `~/.snowflake/cortex/mcp.json`: every server carries an explicit `type`
31535
+ * (`stdio`, `http` or `sse`); a stdio server carries `command` (plus `args`)
31536
+ * and a remote server carries `url`. The canonical `transport` alias and the
31537
+ * Claude-style `httpUrl` alias are folded into `type`/`url`; `env`, `headers`,
31538
+ * `oauth` and `timeout` pass through, as Cortex Code documents all of them.
31539
+ *
31540
+ * A server Cortex Code cannot start or reach — no transport at all, a remote
31541
+ * transport without a URL, a WebSocket URL, or a stdio entry without a
31542
+ * command — is skipped with a warning rather than written in a broken form.
31543
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility#mcp-configuration
31544
+ */
31545
+ function convertToCortexcodeFormat(mcpServers, logger) {
31546
+ const result = {};
31547
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
31548
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
31549
+ if (declaresNoTransport(serverConfig)) {
31550
+ warnAndSkipMcpServer({
31551
+ toolName: "Cortex Code",
31552
+ serverName,
31553
+ reason: "no transport",
31554
+ logger
31555
+ });
31556
+ continue;
31557
+ }
31558
+ const { type, transport, url: _url, httpUrl: _httpUrl, command: _command, args: _args, ...rest } = serverConfig;
31559
+ const converted = {};
31560
+ if (isRemoteMcpServer(serverConfig)) {
31561
+ const url = resolveRemoteMcpUrl(serverConfig);
31562
+ if (!url) {
31563
+ warnAndSkipMcpServer({
31564
+ toolName: "Cortex Code",
31565
+ serverName,
31566
+ reason: "a remote transport without a url",
31567
+ logger
31568
+ });
31569
+ continue;
31570
+ }
31571
+ const stated = type ?? transport;
31572
+ const remoteType = asCortexcodeRemoteType(stated, url);
31573
+ if (remoteType === void 0) {
31574
+ warnAndSkipMcpServer({
31575
+ toolName: "Cortex Code",
31576
+ serverName,
31577
+ reason: stated === void 0 ? "a WebSocket url, which Cortex Code's remote transports (http and sse) cannot reach" : `the "${stated}" transport, which Cortex Code does not offer for remote servers (only http and sse)`,
31578
+ logger
31579
+ });
31580
+ continue;
31581
+ }
31582
+ converted.type = remoteType;
31583
+ converted.url = url;
31584
+ } else {
31585
+ const [command, ...args] = resolveLocalMcpCommand(serverConfig);
31586
+ if (!command) {
31587
+ warnAndSkipMcpServer({
31588
+ toolName: "Cortex Code",
31589
+ serverName,
31590
+ reason: "a stdio transport without a command",
31591
+ logger
31592
+ });
31593
+ continue;
31594
+ }
31595
+ converted.type = "stdio";
31596
+ converted.command = command;
31597
+ if (args.length > 0) converted.args = args;
31598
+ }
31599
+ for (const [key, value] of Object.entries(rest)) {
31600
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
31601
+ converted[key] = (key === "env" || key === "headers") && isRecord$1(value) ? omitPrototypePollutionKeys(value) : value;
31602
+ }
31603
+ result[serverName] = converted;
31604
+ }
31605
+ return result;
31606
+ }
31607
+ /**
31608
+ * Convert Cortex Code's server map back to the canonical shape. The
31609
+ * documented spelling (`type` + `command`/`url`) is already canonical, so
31610
+ * entries pass through with only prototype-pollution keys dropped.
31611
+ */
31612
+ function convertFromCortexcodeFormat(mcpServers) {
31613
+ if (!isMcpServers(mcpServers)) return {};
31614
+ const result = {};
31615
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
31616
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
31617
+ result[serverName] = omitPrototypePollutionKeys(serverConfig);
31618
+ }
31619
+ return result;
31620
+ }
31621
+ /**
31622
+ * Snowflake Cortex Code MCP configuration.
31623
+ *
31624
+ * Cortex Code reads `mcpServers` only from the single user-scoped file
31625
+ * `~/.snowflake/cortex/mcp.json`; no project-scoped MCP location is
31626
+ * documented, so the target is global-only. The file is created and edited by
31627
+ * `cortex mcp add` as well, so `--delete` leaves it in place rather than
31628
+ * removing a file the user did not create through rulesync.
31629
+ *
31630
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility#mcp-configuration
31631
+ */
31632
+ var CortexcodeMcp = class CortexcodeMcp extends ToolMcp {
31633
+ json;
31634
+ constructor(params) {
31635
+ super(params);
31636
+ this.json = this.fileContent === void 0 ? {} : parseCortexcodeMcpConfig({
31637
+ fileContent: this.fileContent,
31638
+ relativePath: join(this.relativeDirPath, this.relativeFilePath)
31639
+ });
31640
+ }
31641
+ getJson() {
31642
+ return this.json;
31643
+ }
31644
+ isDeletable() {
31645
+ return false;
31646
+ }
31647
+ static getSettablePaths(_options = {}) {
31648
+ return {
31649
+ relativeDirPath: CORTEXCODE_GLOBAL_DIR_PATH,
31650
+ relativeFilePath: CORTEXCODE_MCP_FILE_NAME
31651
+ };
31652
+ }
31653
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
31654
+ if (!global) throw new Error(CORTEXCODE_GLOBAL_ONLY_MESSAGE);
31655
+ const paths = this.getSettablePaths({ global });
31656
+ const json = parseCortexcodeMcpConfig({
31657
+ fileContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
31658
+ relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
31659
+ });
31660
+ const newJson = {
31661
+ ...json,
31662
+ mcpServers: json.mcpServers ?? {}
31663
+ };
31664
+ return new CortexcodeMcp({
31665
+ outputRoot,
31666
+ relativeDirPath: paths.relativeDirPath,
31667
+ relativeFilePath: paths.relativeFilePath,
31668
+ fileContent: JSON.stringify(newJson, null, 2),
31669
+ validate,
31670
+ global
31671
+ });
31672
+ }
31673
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
31674
+ if (!global) throw new Error(CORTEXCODE_GLOBAL_ONLY_MESSAGE);
31675
+ const paths = this.getSettablePaths({ global });
31676
+ const json = parseCortexcodeMcpConfig({
31677
+ fileContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2),
31678
+ relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
31679
+ });
31680
+ const mcpServers = convertToCortexcodeFormat(rulesyncMcp.getMcpServers(), logger);
31681
+ const cortexcodeConfig = {
31682
+ ...json,
31683
+ mcpServers
31684
+ };
31685
+ return new CortexcodeMcp({
31686
+ outputRoot,
31687
+ relativeDirPath: paths.relativeDirPath,
31688
+ relativeFilePath: paths.relativeFilePath,
31689
+ fileContent: JSON.stringify(cortexcodeConfig, null, 2),
31690
+ validate,
31691
+ global
31692
+ });
31693
+ }
31694
+ toRulesyncMcp() {
31695
+ const mcpServers = convertFromCortexcodeFormat(this.json.mcpServers);
31696
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
31697
+ }
31698
+ validate() {
31699
+ return {
31700
+ success: true,
31701
+ error: null
31702
+ };
31703
+ }
31704
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
31705
+ return new CortexcodeMcp({
31706
+ outputRoot,
31707
+ relativeDirPath,
31708
+ relativeFilePath,
31709
+ fileContent: "{}",
31710
+ validate: false,
31711
+ global
31712
+ });
31713
+ }
31714
+ };
31715
+ //#endregion
30021
31716
  //#region src/features/mcp/mcp-env-var-format.ts
30022
31717
  /**
30023
31718
  * Canonical rulesync env var reference pattern: `${VAR}` (but not `${env:VAR}`,
@@ -33979,6 +35674,211 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
33979
35674
  }
33980
35675
  };
33981
35676
  //#endregion
35677
+ //#region src/features/mcp/tabnine-mcp.ts
35678
+ /**
35679
+ * The remote `type` Tabnine CLI reads a server as. Tabnine documents `"http"`
35680
+ * (streamable HTTP, the default for a bare `url`) and `"sse"`; `streamable-http`
35681
+ * is the canonical alias for the former. A `ws(s)://` URL or any other stated
35682
+ * transport is something Tabnine cannot reach, so `undefined` tells the caller
35683
+ * to skip the server. `null` means "omit `type`": Tabnine then tries
35684
+ * streamable HTTP first and falls back to SSE on its own.
35685
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-agent/mcp-intro-and-setup/mcp-server-config
35686
+ */
35687
+ function asTabnineRemoteType(stated, url) {
35688
+ if (stated === "sse") return "sse";
35689
+ if (stated === "http" || stated === "streamable-http") return "http";
35690
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : null;
35691
+ }
35692
+ /**
35693
+ * Convert the canonical server map to the shape Tabnine CLI documents for the
35694
+ * `mcpServers` block of `settings.json`: a stdio server carries `command` (plus
35695
+ * `args`), a remote one carries `url` and an optional `type` (`"http"` or
35696
+ * `"sse"`). The canonical `transport` alias and the Claude-style `httpUrl`
35697
+ * alias are folded into `type`/`url`; `enabledTools`/`disabledTools` become
35698
+ * Tabnine's `includeTools`/`excludeTools`; `env`, `cwd`, `headers`, `timeout`
35699
+ * and `trust` pass through as documented.
35700
+ *
35701
+ * A server Tabnine cannot start or reach — no transport at all, a remote
35702
+ * transport without a URL, a WebSocket URL, or a stdio entry without a command
35703
+ * — is skipped with a warning rather than written in a broken form.
35704
+ */
35705
+ function convertToTabnineFormat(mcpServers, logger) {
35706
+ const result = {};
35707
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
35708
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
35709
+ if (declaresNoTransport(serverConfig)) {
35710
+ warnAndSkipMcpServer({
35711
+ toolName: "Tabnine CLI",
35712
+ serverName,
35713
+ reason: "no transport",
35714
+ logger
35715
+ });
35716
+ continue;
35717
+ }
35718
+ const { type, transport, url: _url, httpUrl: _httpUrl, command: _command, args: _args, enabledTools, disabledTools, ...rest } = serverConfig;
35719
+ const converted = {};
35720
+ if (isRemoteMcpServer(serverConfig)) {
35721
+ const url = resolveRemoteMcpUrl(serverConfig);
35722
+ if (!url) {
35723
+ warnAndSkipMcpServer({
35724
+ toolName: "Tabnine CLI",
35725
+ serverName,
35726
+ reason: "a remote transport without a url",
35727
+ logger
35728
+ });
35729
+ continue;
35730
+ }
35731
+ const stated = type ?? transport;
35732
+ const remoteType = asTabnineRemoteType(stated, url);
35733
+ if (remoteType === void 0) {
35734
+ warnAndSkipMcpServer({
35735
+ toolName: "Tabnine CLI",
35736
+ serverName,
35737
+ reason: stated === void 0 ? "a WebSocket url, which Tabnine CLI's remote transports (http and sse) cannot reach" : `the "${stated}" transport, which Tabnine CLI does not offer for remote servers (only http and sse)`,
35738
+ logger
35739
+ });
35740
+ continue;
35741
+ }
35742
+ converted.url = url;
35743
+ if (remoteType !== null) converted.type = remoteType;
35744
+ } else {
35745
+ const [command, ...args] = resolveLocalMcpCommand(serverConfig);
35746
+ if (!command) {
35747
+ warnAndSkipMcpServer({
35748
+ toolName: "Tabnine CLI",
35749
+ serverName,
35750
+ reason: "a stdio transport without a command",
35751
+ logger
35752
+ });
35753
+ continue;
35754
+ }
35755
+ converted.command = command;
35756
+ if (args.length > 0) converted.args = args;
35757
+ }
35758
+ for (const [key, value] of Object.entries(rest)) {
35759
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
35760
+ converted[key] = (key === "env" || key === "headers") && isRecord$1(value) ? omitPrototypePollutionKeys(value) : value;
35761
+ }
35762
+ if (enabledTools !== void 0) converted.includeTools = enabledTools;
35763
+ if (disabledTools !== void 0) converted.excludeTools = disabledTools;
35764
+ result[serverName] = converted;
35765
+ }
35766
+ return result;
35767
+ }
35768
+ /**
35769
+ * Convert Tabnine's server map back to the canonical shape: `includeTools` /
35770
+ * `excludeTools` become `enabledTools` / `disabledTools`; `type` (`stdio`,
35771
+ * `sse`, `http`) and every other documented field are already canonical.
35772
+ */
35773
+ function convertFromTabnineFormat(mcpServers) {
35774
+ if (!isMcpServers(mcpServers)) return {};
35775
+ const result = {};
35776
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
35777
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
35778
+ const converted = {};
35779
+ for (const [key, value] of Object.entries(serverConfig)) {
35780
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
35781
+ if (key === "includeTools") converted.enabledTools = value;
35782
+ else if (key === "excludeTools") converted.disabledTools = value;
35783
+ else converted[key] = value;
35784
+ }
35785
+ result[serverName] = converted;
35786
+ }
35787
+ return result;
35788
+ }
35789
+ /**
35790
+ * Tabnine CLI MCP configuration.
35791
+ *
35792
+ * Tabnine CLI reads `mcpServers` from `<project>/.tabnine/agent/settings.json`
35793
+ * (project scope) and `~/.tabnine/agent/settings.json` (user scope); the
35794
+ * project entry wins when both define the same server name. Both files hold
35795
+ * the user's other settings (`general`, `tools`, `hooks`, ...), so writes go
35796
+ * through the shared-config gateway with `mcpServers` as the only owned key
35797
+ * and the file is never deleted.
35798
+ *
35799
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-agent/mcp-intro-and-setup/mcp-server-config
35800
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings
35801
+ */
35802
+ var TabnineMcp = class TabnineMcp extends ToolMcp {
35803
+ json;
35804
+ constructor(params) {
35805
+ super(params);
35806
+ this.json = parseSharedConfig({
35807
+ format: "json",
35808
+ fileContent: this.fileContent || "{}",
35809
+ filePath: join(this.relativeDirPath, this.relativeFilePath),
35810
+ invalidRootPolicy: "error"
35811
+ });
35812
+ }
35813
+ getJson() {
35814
+ return this.json;
35815
+ }
35816
+ /**
35817
+ * settings.json carries the user's other Tabnine settings, so it must not
35818
+ * be deleted.
35819
+ */
35820
+ isDeletable() {
35821
+ return false;
35822
+ }
35823
+ static getSettablePaths(_options = {}) {
35824
+ return {
35825
+ relativeDirPath: TABNINE_AGENT_DIR_PATH,
35826
+ relativeFilePath: TABNINE_SETTINGS_FILE_NAME
35827
+ };
35828
+ }
35829
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
35830
+ const paths = this.getSettablePaths({ global });
35831
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
35832
+ return new TabnineMcp({
35833
+ outputRoot,
35834
+ relativeDirPath: paths.relativeDirPath,
35835
+ relativeFilePath: paths.relativeFilePath,
35836
+ fileContent,
35837
+ validate,
35838
+ global
35839
+ });
35840
+ }
35841
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
35842
+ const paths = this.getSettablePaths({ global });
35843
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
35844
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({ mcpServers: {} }, null, 2);
35845
+ return new TabnineMcp({
35846
+ outputRoot,
35847
+ relativeDirPath: paths.relativeDirPath,
35848
+ relativeFilePath: paths.relativeFilePath,
35849
+ fileContent: applySharedConfigPatch({
35850
+ fileKey: sharedConfigFileKey(paths),
35851
+ feature: "mcp",
35852
+ existingContent,
35853
+ patch: { mcpServers: convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger) },
35854
+ filePath
35855
+ }),
35856
+ validate,
35857
+ global
35858
+ });
35859
+ }
35860
+ toRulesyncMcp() {
35861
+ const mcpServers = convertFromTabnineFormat(this.json.mcpServers);
35862
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
35863
+ }
35864
+ validate() {
35865
+ return {
35866
+ success: true,
35867
+ error: null
35868
+ };
35869
+ }
35870
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
35871
+ return new TabnineMcp({
35872
+ outputRoot,
35873
+ relativeDirPath,
35874
+ relativeFilePath,
35875
+ fileContent: "{}",
35876
+ validate: false,
35877
+ global
35878
+ });
35879
+ }
35880
+ };
35881
+ //#endregion
33982
35882
  //#region src/features/mcp/takt-mcp.ts
33983
35883
  /**
33984
35884
  * MCP adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
@@ -34938,6 +36838,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
34938
36838
  supportsDisabledTools: false
34939
36839
  }
34940
36840
  }],
36841
+ ["bob", {
36842
+ class: BobMcp,
36843
+ meta: {
36844
+ supportsProject: true,
36845
+ supportsGlobal: true,
36846
+ supportsEnabledTools: false,
36847
+ supportsDisabledTools: false
36848
+ }
36849
+ }],
34941
36850
  ["claudecode", {
34942
36851
  class: ClaudecodeMcp,
34943
36852
  meta: {
@@ -35001,6 +36910,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
35001
36910
  supportsDisabledTools: false
35002
36911
  }
35003
36912
  }],
36913
+ ["cortexcode", {
36914
+ class: CortexcodeMcp,
36915
+ meta: {
36916
+ supportsProject: false,
36917
+ supportsGlobal: true,
36918
+ supportsEnabledTools: false,
36919
+ supportsDisabledTools: false
36920
+ }
36921
+ }],
35004
36922
  ["cursor", {
35005
36923
  class: CursorMcp,
35006
36924
  meta: {
@@ -35172,6 +37090,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
35172
37090
  supportsDisabledTools: false
35173
37091
  }
35174
37092
  }],
37093
+ ["tabnine", {
37094
+ class: TabnineMcp,
37095
+ meta: {
37096
+ supportsProject: true,
37097
+ supportsGlobal: true,
37098
+ supportsEnabledTools: true,
37099
+ supportsDisabledTools: true
37100
+ }
37101
+ }],
35175
37102
  ["takt", {
35176
37103
  class: TaktMcp,
35177
37104
  meta: {
@@ -36376,7 +38303,7 @@ const ANTIGRAVITY_CLI_OVERRIDE_KEYS = [
36376
38303
  "allowNonWorkspaceAccess",
36377
38304
  "agentMode"
36378
38305
  ];
36379
- const moduleLogger$3 = fallbackLogger;
38306
+ const moduleLogger$4 = fallbackLogger;
36380
38307
  /**
36381
38308
  * Narrow an imported `settings.json` value to one of the documented values of
36382
38309
  * an enum-typed override key. The override schema declares `toolPermission`,
@@ -36390,7 +38317,7 @@ const moduleLogger$3 = fallbackLogger;
36390
38317
  function pickDocumentedValue({ key, value, documented }) {
36391
38318
  if (typeof value !== "string") return void 0;
36392
38319
  if (documented.includes(value)) return value;
36393
- moduleLogger$3.warn(`Antigravity CLI permissions: dropping '${key}: ${value}' on import — it is not one of the documented values (${documented.join(", ")}), and carrying it through would fail validation of the whole permissions file. Author it under the 'antigravity-cli' override by hand once rulesync supports it.`);
38320
+ moduleLogger$4.warn(`Antigravity CLI permissions: dropping '${key}: ${value}' on import — it is not one of the documented values (${documented.join(", ")}), and carrying it through would fail validation of the whole permissions file. Author it under the 'antigravity-cli' override by hand once rulesync supports it.`);
36394
38321
  }
36395
38322
  /**
36396
38323
  * Deliberate mapping from rulesync canonical permission categories to the
@@ -36862,7 +38789,7 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
36862
38789
  }
36863
38790
  //#endregion
36864
38791
  //#region src/features/permissions/augmentcode-permissions.ts
36865
- const moduleLogger$2 = fallbackLogger;
38792
+ const moduleLogger$3 = fallbackLogger;
36866
38793
  z.enum([
36867
38794
  "allow",
36868
38795
  "deny",
@@ -37161,7 +39088,7 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
37161
39088
  const specialEntries = allEntries.filter((entry) => isSpecialEntry(entry));
37162
39089
  const result = { ...convertAugmentToRulesyncPermissions({
37163
39090
  entries: allEntries.filter((entry) => !isSpecialEntry(entry)),
37164
- logger: moduleLogger$2
39091
+ logger: moduleLogger$3
37165
39092
  }) };
37166
39093
  if (specialEntries.length > 0) result.augmentcode = { toolPermissions: specialEntries };
37167
39094
  return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
@@ -37535,6 +39462,14 @@ function collectRestrictionLosingSandboxEntries({ existing, merged, paths, toolL
37535
39462
  return entries;
37536
39463
  }
37537
39464
  /**
39465
+ * The `'<label>' — <reason>` list a trust-affecting warning ends with, shared so
39466
+ * an adapter that phrases the rest of the sentence itself (an import that lifts
39467
+ * the settings rather than writes them) prints the entries the same way.
39468
+ */
39469
+ function formatTrustAffectingEntries(entries) {
39470
+ return entries.map(({ label, reason }) => `'${label}' — ${reason}`).join("; ");
39471
+ }
39472
+ /**
37538
39473
  * The one warning that names every trust-affecting setting this generate wrote
37539
39474
  * to `relativeFilePath`. Emitted once per file: the individual reasons are what
37540
39475
  * matter, but the "review this as you would a hook" framing only needs saying
@@ -37545,7 +39480,7 @@ function collectRestrictionLosingSandboxEntries({ existing, merged, paths, toolL
37545
39480
  function warnOnTrustAffectingEntries({ toolLabel, noun = "setting", entries, relativeFilePath, logger }) {
37546
39481
  if (entries.length === 0) return;
37547
39482
  const one = entries.length === 1;
37548
- const details = entries.map(({ label, reason }) => `'${label}' — ${reason}`).join("; ");
39483
+ const details = formatTrustAffectingEntries(entries);
37549
39484
  logger?.warn(`${toolLabel} permissions: writing ${entries.length} trust-affecting ${noun}${one ? "" : "s"} to ${relativeFilePath}; review ${one ? "it" : "them"} as you would a hook, especially if this permissions file came from 'rulesync fetch'. ${details}.`);
37550
39485
  }
37551
39486
  //#endregion
@@ -43667,7 +45602,7 @@ const QwenSettingsPermissionsSchema = z.looseObject({
43667
45602
  deny: z.optional(z.array(z.string()))
43668
45603
  });
43669
45604
  const QwenSettingsSchema = z.looseObject({ permissions: z.optional(QwenSettingsPermissionsSchema) });
43670
- const moduleLogger$1 = fallbackLogger;
45605
+ const moduleLogger$2 = fallbackLogger;
43671
45606
  /**
43672
45607
  * Mapping from rulesync canonical tool category names (lowercase) to Qwen Code tool names (PascalCase).
43673
45608
  * Unknown names pass through as-is (e.g., mcp__server__tool).
@@ -44030,7 +45965,7 @@ function warnAboutImportedScopedKeys(groups) {
44030
45965
  if (!announceOnImport) continue;
44031
45966
  if (announceOnlyGrants && !grantsSomething(value, scopedKey)) continue;
44032
45967
  const globalNote = scopedKey.globalNote ?? QWEN_SCOPE_RULES[rule].globalNote;
44033
- moduleLogger$1.warn(`Qwen permissions: imported ${quoteQualifiedKey({
45968
+ moduleLogger$2.warn(`Qwen permissions: imported ${quoteQualifiedKey({
44034
45969
  groupName,
44035
45970
  key
44036
45971
  })} = ${quoteValueForWarning(value)}. ${globalNote} Review it before generating with the global scope.`);
@@ -44223,7 +46158,7 @@ function convertRulesyncToQwenPermissions(config) {
44223
46158
  }
44224
46159
  function convertQwenToRulesyncPermissions(params) {
44225
46160
  const permission = {};
44226
- const logger = params.logger ?? moduleLogger$1;
46161
+ const logger = params.logger ?? moduleLogger$2;
44227
46162
  const processEntries = (entries, action) => {
44228
46163
  for (const entry of entries) {
44229
46164
  const parsed = parseQwenPermissionEntry(entry, { logger });
@@ -45549,6 +47484,585 @@ function isPermissionAction(value) {
45549
47484
  return PermissionActionSchema.safeParse(value).success;
45550
47485
  }
45551
47486
  //#endregion
47487
+ //#region src/features/permissions/tabnine-permissions.ts
47488
+ /** The `settings.json` key group that carries the two tool lists. */
47489
+ const TOOLS_KEY = "tools";
47490
+ /** `tools.allowed`: tool names (and `run_shell_command(<prefix>)`) that skip the confirmation prompt. */
47491
+ const ALLOWED_KEY = "allowed";
47492
+ /** `tools.exclude`: tool names removed from discovery entirely. */
47493
+ const EXCLUDE_KEY = "exclude";
47494
+ /** The `settings.json` key group whose keys the `tabnine` override may author. */
47495
+ const GENERAL_KEY = "general";
47496
+ /** The Tabnine CLI built-in that runs shell commands. */
47497
+ const SHELL_TOOL_NAME = "run_shell_command";
47498
+ /**
47499
+ * The paths of the `tabnine` override that are refused with a warning rather
47500
+ * than written — the line Claude Code's sandbox paths draw. Under `tools`, the
47501
+ * values Tabnine CLI spawns as a command (`discoveryCommand` and `callCommand`
47502
+ * at startup, `shell.pager` on every shell output, `sandbox.command` to start
47503
+ * the sandbox); under `general`, the host every login and API call goes to
47504
+ * (`tabnineHost`) and the editor executable (`preferredEditor`, "any editor
47505
+ * path"). A fetched `.rulesync/permissions.jsonc` must not be able to point
47506
+ * Tabnine CLI at an executable, or a server, of its choosing.
47507
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference
47508
+ */
47509
+ const REFUSED_OVERRIDE_PATHS = {
47510
+ [TOOLS_KEY]: [
47511
+ ["discoveryCommand"],
47512
+ ["callCommand"],
47513
+ ["shell", "pager"],
47514
+ ["sandbox", "command"]
47515
+ ],
47516
+ [GENERAL_KEY]: [["tabnineHost"], ["preferredEditor"]]
47517
+ };
47518
+ /** `tools.sandbox`: `true` keeps tool calls contained; anything else disables or picks the sandbox. */
47519
+ const SANDBOX_KEY = "sandbox";
47520
+ /** `general.defaultApprovalMode`: the modes that still ask before a tool call runs. */
47521
+ const DEFAULT_APPROVAL_MODE_KEY = "defaultApprovalMode";
47522
+ const PROMPTING_APPROVAL_MODES = /* @__PURE__ */ new Set(["default", "plan"]);
47523
+ /**
47524
+ * Canonical categories with a one-to-one Tabnine CLI built-in tool. `bash` is
47525
+ * handled separately because it is the only tool whose list entries carry a
47526
+ * command prefix.
47527
+ *
47528
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/built-in-tools
47529
+ */
47530
+ const CANONICAL_TO_TABNINE_TOOL_NAMES = {
47531
+ read: "read_file",
47532
+ edit: "replace",
47533
+ write: "write_file",
47534
+ webfetch: "web_fetch",
47535
+ websearch: "google_web_search",
47536
+ grep: "grep_search",
47537
+ glob: "glob"
47538
+ };
47539
+ const TABNINE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_TABNINE_TOOL_NAMES).map(([canonical, tabnine]) => [tabnine, canonical]));
47540
+ const moduleLogger$1 = fallbackLogger;
47541
+ /**
47542
+ * Turn a canonical `bash` glob into the command prefix a
47543
+ * `run_shell_command(<prefix>)` entry carries. Tabnine matches the prefix
47544
+ * against the start of the command line, so only two glob shapes map cleanly:
47545
+ * `<prefix> *` and a bare `<prefix>` with no wildcard at all. Anything else
47546
+ * (`git * push`, `*.sh`, `npm run:*`) matches commands a prefix cannot name, so
47547
+ * it is reported and left out rather than approximated. `*` alone names the
47548
+ * tool itself and is returned as the empty prefix.
47549
+ */
47550
+ function toShellPrefix(pattern) {
47551
+ if (pattern === "*") return "";
47552
+ const prefix = pattern.endsWith(" *") ? pattern.slice(0, -2) : pattern;
47553
+ if (prefix.length === 0 || /[*?[\]]/.test(prefix)) return;
47554
+ return prefix;
47555
+ }
47556
+ /**
47557
+ * The Tabnine tool a canonical category names. An unknown category is passed
47558
+ * through as a tool name (that is how an MCP tool is named); the read is an
47559
+ * own-property lookup so a category spelled `toString` does not pick up an
47560
+ * `Object.prototype` member.
47561
+ */
47562
+ function toTabnineToolName(category) {
47563
+ return lookupOwn({
47564
+ record: CANONICAL_TO_TABNINE_TOOL_NAMES,
47565
+ key: category
47566
+ }) ?? category;
47567
+ }
47568
+ /**
47569
+ * The widest glob a `bash` pattern stands for once it is written as a
47570
+ * `run_shell_command(<prefix>)` entry: the prefix matches the start of the
47571
+ * command line, so a bare `pnpm` also covers `pnpm install`. Used only to
47572
+ * compare allows against the restrictions (`bash` deny and `ask` rules, and
47573
+ * the rules of `*`), where widening can only withhold more, never fail open.
47574
+ */
47575
+ function widenToPrefixGlob(pattern) {
47576
+ const prefix = toShellPrefix(pattern);
47577
+ if (prefix === void 0) return pattern;
47578
+ return prefix === "" ? "*" : `${prefix}*`;
47579
+ }
47580
+ function toShellEntry(prefix) {
47581
+ return prefix === "" ? SHELL_TOOL_NAME : `${SHELL_TOOL_NAME}(${prefix})`;
47582
+ }
47583
+ /** The `bash` pattern a `run_shell_command(<prefix>)` entry stands for. */
47584
+ function toShellPattern(prefix) {
47585
+ return prefix === void 0 || prefix === "" ? "*" : `${prefix} *`;
47586
+ }
47587
+ /**
47588
+ * Split a `tools.allowed`/`tools.exclude` entry into its tool name and the
47589
+ * optional `(<prefix>)` suffix. An entry whose parenthesis never closes, or
47590
+ * that carries text after the closing one, is malformed and yields `undefined`.
47591
+ */
47592
+ function parseTabnineEntry(entry) {
47593
+ const open = entry.indexOf("(");
47594
+ if (open === -1) return entry.length > 0 ? {
47595
+ toolName: entry,
47596
+ prefix: void 0
47597
+ } : void 0;
47598
+ if (open === 0 || !entry.endsWith(")")) return;
47599
+ return {
47600
+ toolName: entry.slice(0, open),
47601
+ prefix: entry.slice(open + 1, -1)
47602
+ };
47603
+ }
47604
+ /**
47605
+ * `record` without the value at `path`, when there is one, and without any
47606
+ * container the removal emptied. Containers on the way are copied, never
47607
+ * mutated, so the override the caller read stays intact.
47608
+ */
47609
+ function omitPath({ record, path }) {
47610
+ const [head, ...rest] = path;
47611
+ if (head === void 0 || !Object.hasOwn(record, head)) return {
47612
+ record,
47613
+ removed: false
47614
+ };
47615
+ if (rest.length === 0) {
47616
+ const { [head]: _removed, ...others } = record;
47617
+ return {
47618
+ record: others,
47619
+ removed: true
47620
+ };
47621
+ }
47622
+ const child = record[head];
47623
+ if (!isRecord$1(child)) return {
47624
+ record,
47625
+ removed: false
47626
+ };
47627
+ const inner = omitPath({
47628
+ record: child,
47629
+ path: rest
47630
+ });
47631
+ if (!inner.removed) return {
47632
+ record,
47633
+ removed: false
47634
+ };
47635
+ if (Object.keys(inner.record).length === 0) {
47636
+ const { [head]: _emptied, ...others } = record;
47637
+ return {
47638
+ record: others,
47639
+ removed: true
47640
+ };
47641
+ }
47642
+ return {
47643
+ record: {
47644
+ ...record,
47645
+ [head]: inner.record
47646
+ },
47647
+ removed: true
47648
+ };
47649
+ }
47650
+ /**
47651
+ * The `tabnine.<group>` record with every refused path removed, and the dotted
47652
+ * names of the paths that were there. Nothing else of the group is touched:
47653
+ * the rest is written verbatim (and announced where it widens trust).
47654
+ */
47655
+ function stripRefusedPaths({ group, record }) {
47656
+ const refused = [];
47657
+ let stripped = record;
47658
+ for (const path of REFUSED_OVERRIDE_PATHS[group]) {
47659
+ const result = omitPath({
47660
+ record: stripped,
47661
+ path
47662
+ });
47663
+ if (result.removed) {
47664
+ refused.push(`${group}.${path.join(".")}`);
47665
+ stripped = result.record;
47666
+ }
47667
+ }
47668
+ return {
47669
+ record: stripped,
47670
+ refused
47671
+ };
47672
+ }
47673
+ /**
47674
+ * The `tools` and `general` groups of an override with the refused paths taken
47675
+ * out, and every refused path named. A `general` that is absent or that the
47676
+ * refusals emptied is reported as absent so no `general: {}` is written or
47677
+ * lifted. (The `TabninePermissionsOverrideSchema` already rejects a group that
47678
+ * is not an object; the `isRecord` guards only keep an unvalidated file from
47679
+ * throwing.)
47680
+ */
47681
+ function stripRefusedOverridePaths(override) {
47682
+ const tools = stripRefusedPaths({
47683
+ group: TOOLS_KEY,
47684
+ record: isRecord$1(override[TOOLS_KEY]) ? override[TOOLS_KEY] : {}
47685
+ });
47686
+ const general = isRecord$1(override[GENERAL_KEY]) ? stripRefusedPaths({
47687
+ group: GENERAL_KEY,
47688
+ record: override[GENERAL_KEY]
47689
+ }) : void 0;
47690
+ return {
47691
+ tools: tools.record,
47692
+ general: general !== void 0 && Object.keys(general.record).length > 0 ? general.record : void 0,
47693
+ refused: [...tools.refused, ...general?.refused ?? []]
47694
+ };
47695
+ }
47696
+ /**
47697
+ * The settings of a `tabnine` override that widen what Tabnine CLI does without
47698
+ * asking, so the generate can name them. `tools.allowed` counts because an
47699
+ * override-authored entry skips the confirmation prompt on the strength of the
47700
+ * override alone (its `run_shell_command(...)` entries are not here: they are
47701
+ * compared as `bash` allows and withheld like them); `tools.sandbox` and
47702
+ * `general.defaultApprovalMode` decide whether tool calls are contained and
47703
+ * whether they are confirmed at all. Called on the override, not the file, so a
47704
+ * value the file already held is not reported as something rulesync opened.
47705
+ */
47706
+ function collectTrustAffectingOverrideEntries({ tools, general }) {
47707
+ const entries = [];
47708
+ const allowed = isStringArray$2(tools[ALLOWED_KEY]) ? tools[ALLOWED_KEY] : [];
47709
+ if (allowed.length > 0) entries.push({
47710
+ label: `tools.allowed (${allowed.map(quoteValueForWarning).join(", ")})`,
47711
+ reason: "auto-approves what it names as the override spells it; a non-shell entry has no canonical rule to be checked against"
47712
+ });
47713
+ if (Object.hasOwn(tools, SANDBOX_KEY) && isNotTrue(tools[SANDBOX_KEY])) entries.push({
47714
+ label: `tools.sandbox = ${quoteValueForWarning(tools[SANDBOX_KEY])}`,
47715
+ reason: "is not the plain `true` that keeps tool calls contained, so it either disables the sandbox or picks the command, image, paths and network that contain them"
47716
+ });
47717
+ const approvalMode = general?.[DEFAULT_APPROVAL_MODE_KEY];
47718
+ if (general !== void 0 && Object.hasOwn(general, DEFAULT_APPROVAL_MODE_KEY) && !PROMPTING_APPROVAL_MODES.has(approvalMode)) entries.push({
47719
+ label: `general.defaultApprovalMode = ${quoteValueForWarning(approvalMode)}`,
47720
+ reason: "auto-approves tool calls instead of asking; only `default` and `plan` keep the prompt"
47721
+ });
47722
+ return entries;
47723
+ }
47724
+ /**
47725
+ * What a later generate would announce from the override an import builds,
47726
+ * said now, so the file the import writes into `.rulesync/` is read with the
47727
+ * same eyes as one that arrived with a clone. `refused` names the paths the
47728
+ * import left in `settings.json`, the ones generate refuses to write — an
47729
+ * override must not carry a value that only ever produces a warning.
47730
+ */
47731
+ function announceLiftedOverride({ tools, general, refused }) {
47732
+ if (refused.length > 0) {
47733
+ const one = refused.length === 1;
47734
+ moduleLogger$1.info(`Tabnine CLI permissions: left ${refused.join(", ")} in settings.json rather than lifting ${one ? "it" : "them"} into the tabnine override; Tabnine CLI runs ${one ? "that value" : "those values"} as a command or sends its traffic there, and 'rulesync generate' refuses to write ${one ? "it" : "them"} from the override.`);
47735
+ }
47736
+ const trustAffecting = collectTrustAffectingOverrideEntries({
47737
+ tools,
47738
+ general
47739
+ });
47740
+ if (trustAffecting.length > 0) {
47741
+ const one = trustAffecting.length === 1;
47742
+ moduleLogger$1.warn(`Tabnine CLI permissions: the tabnine override now carries ${trustAffecting.length} trust-affecting setting${one ? "" : "s"} that 'rulesync generate' writes back as authored: ${formatTrustAffectingEntries(trustAffecting)}.`);
47743
+ }
47744
+ }
47745
+ /**
47746
+ * Build the two tool lists from the canonical block; the rules that could not
47747
+ * be carried are reported through `logger` here.
47748
+ */
47749
+ function buildToolLists({ permission, overrideShellAllowPatterns, logger }) {
47750
+ const allowed = [];
47751
+ const exclude = [];
47752
+ const { rules: canonicalRules, ignoredAllToolsAllowPatterns } = collectShellCommandRules(permission);
47753
+ const { allow: shellAllow, deny: shellDeny, shadowedAllowPatterns, unwrittenDenyPatterns, unenforcedAllToolsAskPatterns, intersectionBudgetExhausted } = partitionCommandRules({
47754
+ rules: [...canonicalRules, ...overrideShellAllowPatterns.map((pattern) => ({
47755
+ pattern,
47756
+ action: "allow",
47757
+ fromAllToolsCategory: false
47758
+ }))],
47759
+ writesAllToolsDeny: false,
47760
+ normalizePattern: widenToPrefixGlob
47761
+ });
47762
+ warnAboutUnwrittenCommandRules({
47763
+ toolLabel: "Tabnine CLI",
47764
+ surfaceLabel: "tools.allowed/tools.exclude",
47765
+ foreignRestrictingCategories: [],
47766
+ shadowedAllowPatterns,
47767
+ unwrittenDenyPatterns,
47768
+ unwrittenDenyReason: "tools.exclude removes a tool from discovery, and a pattern written under '*' need not be a command at all.",
47769
+ unenforcedAllToolsAskPatterns,
47770
+ ignoredAllToolsAllowPatterns,
47771
+ intersectionBudgetExhausted,
47772
+ logger
47773
+ });
47774
+ const unmappedShellPatterns = [];
47775
+ for (const pattern of shellDeny) {
47776
+ const prefix = toShellPrefix(pattern);
47777
+ if (prefix === void 0) {
47778
+ unmappedShellPatterns.push(pattern);
47779
+ continue;
47780
+ }
47781
+ exclude.push(toShellEntry(prefix));
47782
+ }
47783
+ const shadowingShellDenies = createShadowingRestrictionsTest(shellDeny.map((pattern) => ({
47784
+ pattern,
47785
+ fromAllToolsCategory: false
47786
+ })), { normalizePattern: widenToPrefixGlob });
47787
+ const withheldAllowPatterns = [];
47788
+ for (const pattern of shellAllow) {
47789
+ const prefix = toShellPrefix(pattern);
47790
+ if (prefix === void 0) {
47791
+ unmappedShellPatterns.push(pattern);
47792
+ continue;
47793
+ }
47794
+ if (shadowingShellDenies(pattern).length > 0) {
47795
+ withheldAllowPatterns.push(pattern);
47796
+ continue;
47797
+ }
47798
+ allowed.push(toShellEntry(prefix));
47799
+ }
47800
+ if (unmappedShellPatterns.length > 0) warnWithFallback(logger, `Tabnine CLI permissions: skipped ${unmappedShellPatterns.length} 'bash' rule(s) whose pattern is not a command prefix (${unmappedShellPatterns.map(quoteValueForWarning).join(", ")}). run_shell_command(<prefix>) matches the start of the command line, so only '<prefix> *' and a bare '<prefix>' can be written.`);
47801
+ if (withheldAllowPatterns.length > 0) warnWithFallback(logger, `Tabnine CLI permissions: withheld ${withheldAllowPatterns.length} 'bash' allow rule(s) (${withheldAllowPatterns.map(quoteValueForWarning).join(", ")}) that overlap a 'bash' deny rule; writing them could auto-approve the denied commands, since tools.exclude is not documented to narrow run_shell_command to a prefix.`);
47802
+ const unmappedPatterns = [];
47803
+ for (const [category, categoryRules] of Object.entries(permission)) {
47804
+ if (category === "bash" || category === "*") continue;
47805
+ const toolName = toTabnineToolName(category);
47806
+ for (const [pattern, action] of Object.entries(categoryRules)) {
47807
+ if (pattern !== "*") {
47808
+ unmappedPatterns.push(`${category}: ${pattern}`);
47809
+ continue;
47810
+ }
47811
+ if (action === "allow") allowed.push(toolName);
47812
+ else if (action === "deny") exclude.push(toolName);
47813
+ }
47814
+ }
47815
+ if (unmappedPatterns.length > 0) warnWithFallback(logger, `Tabnine CLI permissions: skipped ${unmappedPatterns.length} rule(s) with a pattern other than '*' (${unmappedPatterns.map(quoteValueForWarning).join(", ")}). tools.allowed/tools.exclude name whole tools; only run_shell_command takes a prefix.`);
47816
+ return {
47817
+ allowed: uniq(allowed),
47818
+ exclude: uniq(exclude)
47819
+ };
47820
+ }
47821
+ /**
47822
+ * Permissions generator for the Tabnine CLI.
47823
+ *
47824
+ * Tabnine CLI keeps its tool policy in `.tabnine/agent/settings.json` (project)
47825
+ * and `~/.tabnine/agent/settings.json` (user), a shared file that also carries
47826
+ * `mcpServers`, `hooks`, `general.*`, `context.*` and more, so writes go through
47827
+ * the shared-config gateway as a deep merge and the file is never deleted.
47828
+ *
47829
+ * Two keys are driven by the canonical block:
47830
+ * - `tools.allowed` — `allow` rules. A tool name skips the confirmation prompt;
47831
+ * `run_shell_command(<prefix>)` narrows that to one command prefix.
47832
+ * - `tools.exclude` — `deny` rules. A tool name removes the tool from discovery.
47833
+ * A `bash` deny is written as `run_shell_command(<prefix>)` there, the form
47834
+ * Gemini CLI's `excludeTools` (which Tabnine derives from) honors, but Tabnine
47835
+ * documents the prefix for `tools.allowed` only — so the entry is not relied
47836
+ * on, and every allow the deny overlaps is withheld as well.
47837
+ *
47838
+ * `ask` writes nothing: a tool that is in neither list keeps Tabnine's default
47839
+ * prompt. Every other `tools.*` key (`core`, `shell.*`, `enableWebTools`, ...)
47840
+ * and the `general.*` group are authored through the `tabnine` override, which
47841
+ * is deep-merged beneath the two canonical lists; entries in the lists that
47842
+ * cannot be mapped back (a prefix on a non-shell tool, an MCP tool name) round
47843
+ * trip through the override too.
47844
+ *
47845
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/built-in-tools
47846
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference
47847
+ */
47848
+ var TabninePermissions = class TabninePermissions extends ToolPermissions {
47849
+ constructor(params) {
47850
+ super({
47851
+ ...params,
47852
+ fileContent: params.fileContent ?? "{}"
47853
+ });
47854
+ }
47855
+ /**
47856
+ * `settings.json` holds unrelated user settings (`mcpServers`, `hooks`,
47857
+ * `general`, ...), so it must not be deleted.
47858
+ */
47859
+ isDeletable() {
47860
+ return false;
47861
+ }
47862
+ static getSettablePaths(_options = {}) {
47863
+ return {
47864
+ relativeDirPath: TABNINE_AGENT_DIR_PATH,
47865
+ relativeFilePath: TABNINE_SETTINGS_FILE_NAME
47866
+ };
47867
+ }
47868
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
47869
+ const paths = TabninePermissions.getSettablePaths({ global });
47870
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
47871
+ return new TabninePermissions({
47872
+ outputRoot,
47873
+ relativeDirPath: paths.relativeDirPath,
47874
+ relativeFilePath: paths.relativeFilePath,
47875
+ fileContent,
47876
+ validate,
47877
+ global
47878
+ });
47879
+ }
47880
+ static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
47881
+ const paths = TabninePermissions.getSettablePaths({ global });
47882
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
47883
+ const existingContent = await readFileContentOrNull(filePath) ?? "{}";
47884
+ const config = rulesyncPermissions.getJson();
47885
+ const override = isRecord$1(config.tabnine) ? config.tabnine : {};
47886
+ const { tools: overrideTools, general: overrideGeneral, refused: refusedPaths } = stripRefusedOverridePaths(override);
47887
+ if (refusedPaths.length > 0) warnWithFallback(logger, `Tabnine CLI permissions: refused to write ${refusedPaths.join(", ")} from the tabnine override; Tabnine CLI runs that value as a command or sends its traffic to it, and a permissions file (one that came from 'rulesync fetch' included) must not be able to point it at an executable or a server of its choosing. Set it by hand in ${join(paths.relativeDirPath, paths.relativeFilePath)} if you need it.`);
47888
+ const overrideList = (key) => {
47889
+ const value = overrideTools[key];
47890
+ if (value === void 0 || isStringArray$2(value)) return value ?? [];
47891
+ warnWithFallback(logger, `Tabnine CLI permissions: ignored tools.${key} of the tabnine override (${quoteValueForWarning(value)}); it must be a list of tool names.`);
47892
+ return [];
47893
+ };
47894
+ const overrideShellAllowPatterns = [];
47895
+ const overrideVerbatimAllowed = [];
47896
+ for (const entry of overrideList(ALLOWED_KEY)) {
47897
+ const parsed = parseTabnineEntry(entry);
47898
+ if (parsed?.toolName === SHELL_TOOL_NAME) overrideShellAllowPatterns.push(toShellPattern(parsed.prefix));
47899
+ else overrideVerbatimAllowed.push(entry);
47900
+ }
47901
+ const overrideExclude = overrideList(EXCLUDE_KEY);
47902
+ const { allowed, exclude } = buildToolLists({
47903
+ permission: config.permission,
47904
+ overrideShellAllowPatterns,
47905
+ logger
47906
+ });
47907
+ const ignoredOverrideKeys = Object.keys(override).filter((key) => key !== TOOLS_KEY && key !== GENERAL_KEY);
47908
+ if (ignoredOverrideKeys.length > 0) warnWithFallback(logger, `Tabnine CLI permissions: ignored ${ignoredOverrideKeys.length} key(s) of the tabnine override (${ignoredOverrideKeys.map(quoteValueForWarning).join(", ")}); only 'tools' and 'general' are written through the permissions feature.`);
47909
+ const existingTools = this.existingToolsGroup(existingContent);
47910
+ const managedToolNames = new Set(Object.keys(config.permission).map((category) => category === "bash" || category === "*" ? SHELL_TOOL_NAME : toTabnineToolName(category)));
47911
+ const preservedEntries = (key) => (isStringArray$2(existingTools?.[key]) ? existingTools[key] : []).filter((entry) => {
47912
+ const toolName = parseTabnineEntry(entry)?.toolName;
47913
+ return toolName === void 0 || !managedToolNames.has(toolName);
47914
+ });
47915
+ const allowedList = uniq([
47916
+ ...allowed,
47917
+ ...overrideVerbatimAllowed,
47918
+ ...preservedEntries(ALLOWED_KEY)
47919
+ ]);
47920
+ const excludeList = uniq([
47921
+ ...exclude,
47922
+ ...overrideExclude,
47923
+ ...preservedEntries(EXCLUDE_KEY)
47924
+ ]);
47925
+ const droppedExcludeEntries = uniq(isStringArray$2(existingTools?.[EXCLUDE_KEY]) ? existingTools[EXCLUDE_KEY] : []).filter((entry) => {
47926
+ if (excludeList.includes(entry)) return false;
47927
+ const toolName = parseTabnineEntry(entry)?.toolName;
47928
+ return toolName === void 0 || !excludeList.includes(toolName);
47929
+ });
47930
+ if (droppedExcludeEntries.length > 0) warnWithFallback(logger, `Tabnine CLI permissions: removed ${droppedExcludeEntries.length} existing tools.exclude entry(ies) (${droppedExcludeEntries.map(quoteValueForWarning).join(", ")}) of a tool the canonical block manages; add a deny rule to .rulesync/permissions.jsonc to keep them.`);
47931
+ const tools = {
47932
+ ...overrideTools,
47933
+ [ALLOWED_KEY]: allowedList.length > 0 ? allowedList : void 0,
47934
+ [EXCLUDE_KEY]: excludeList.length > 0 ? excludeList : void 0
47935
+ };
47936
+ const patch = {};
47937
+ if (overrideGeneral !== void 0) patch[GENERAL_KEY] = overrideGeneral;
47938
+ if (Object.values(tools).some((value) => value !== void 0) || existingTools !== void 0) patch[TOOLS_KEY] = tools;
47939
+ warnOnTrustAffectingEntries({
47940
+ toolLabel: "Tabnine CLI",
47941
+ entries: collectTrustAffectingOverrideEntries({
47942
+ tools: {
47943
+ ...overrideTools,
47944
+ [ALLOWED_KEY]: overrideVerbatimAllowed
47945
+ },
47946
+ general: overrideGeneral
47947
+ }),
47948
+ relativeFilePath: join(paths.relativeDirPath, paths.relativeFilePath),
47949
+ logger
47950
+ });
47951
+ return new TabninePermissions({
47952
+ outputRoot,
47953
+ relativeDirPath: paths.relativeDirPath,
47954
+ relativeFilePath: paths.relativeFilePath,
47955
+ fileContent: applySharedConfigPatch({
47956
+ fileKey: sharedConfigFileKey(paths),
47957
+ feature: "permissions",
47958
+ existingContent,
47959
+ patch,
47960
+ filePath
47961
+ }),
47962
+ validate: true,
47963
+ global
47964
+ });
47965
+ }
47966
+ /** The `tools` group of the existing file, or `undefined` when it has none. */
47967
+ static existingToolsGroup(existingContent) {
47968
+ try {
47969
+ const existing = parseSharedConfig({
47970
+ format: "json",
47971
+ fileContent: existingContent || "{}",
47972
+ invalidRootPolicy: "error"
47973
+ });
47974
+ return isRecord$1(existing[TOOLS_KEY]) ? existing[TOOLS_KEY] : void 0;
47975
+ } catch {
47976
+ return;
47977
+ }
47978
+ }
47979
+ toRulesyncPermissions() {
47980
+ const settings = this.parseSettings();
47981
+ const tools = isRecord$1(settings[TOOLS_KEY]) ? settings[TOOLS_KEY] : {};
47982
+ const permission = {};
47983
+ const leftovers = {};
47984
+ const setRule = (category, pattern, action) => {
47985
+ const rules = Object.hasOwn(permission, category) ? permission[category] : permission[category] = {};
47986
+ if (rules !== void 0 && !Object.hasOwn(rules, pattern)) rules[pattern] = action;
47987
+ };
47988
+ for (const [key, action] of [[EXCLUDE_KEY, "deny"], [ALLOWED_KEY, "allow"]]) {
47989
+ const entries = isStringArray$2(tools[key]) ? tools[key] : [];
47990
+ for (const entry of entries) {
47991
+ const parsed = parseTabnineEntry(entry);
47992
+ if (parsed === void 0) {
47993
+ moduleLogger$1.warn(`Tabnine CLI permissions: kept malformed tools.${key} entry ${quoteValueForWarning(entry)} in the tabnine override.`);
47994
+ (leftovers[key] ??= []).push(entry);
47995
+ continue;
47996
+ }
47997
+ const { toolName, prefix } = parsed;
47998
+ if (toolName === SHELL_TOOL_NAME) {
47999
+ setRule(SHELL_PERMISSION_CATEGORY, toShellPattern(prefix), action);
48000
+ continue;
48001
+ }
48002
+ if (prefix !== void 0) {
48003
+ (leftovers[key] ??= []).push(entry);
48004
+ continue;
48005
+ }
48006
+ const category = lookupOwn({
48007
+ record: TABNINE_TO_CANONICAL_TOOL_NAMES,
48008
+ key: toolName
48009
+ }) ?? toolName;
48010
+ if (isPrototypePollutionKey(category)) {
48011
+ moduleLogger$1.warn(`Tabnine CLI permissions: kept tools.${key} entry ${quoteValueForWarning(entry)} in the tabnine override; its name cannot be a permission category.`);
48012
+ (leftovers[key] ??= []).push(entry);
48013
+ continue;
48014
+ }
48015
+ setRule(category, "*", action);
48016
+ }
48017
+ }
48018
+ const { tools: overrideTools, general, refused } = stripRefusedOverridePaths({
48019
+ [TOOLS_KEY]: Object.fromEntries(Object.entries(tools).filter(([key]) => key !== ALLOWED_KEY && key !== EXCLUDE_KEY)),
48020
+ [GENERAL_KEY]: settings[GENERAL_KEY]
48021
+ });
48022
+ for (const [key, entries] of Object.entries(leftovers)) overrideTools[key] = entries;
48023
+ const override = {};
48024
+ if (Object.keys(overrideTools).length > 0) override[TOOLS_KEY] = overrideTools;
48025
+ if (general !== void 0) override[GENERAL_KEY] = general;
48026
+ announceLiftedOverride({
48027
+ tools: overrideTools,
48028
+ general,
48029
+ refused
48030
+ });
48031
+ const imported = { permission };
48032
+ if (Object.keys(override).length > 0) imported.tabnine = override;
48033
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(imported, null, 2) });
48034
+ }
48035
+ parseSettings() {
48036
+ const relativePath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
48037
+ try {
48038
+ return parseSharedConfig({
48039
+ format: "json",
48040
+ fileContent: this.getFileContent() || "{}",
48041
+ filePath: relativePath,
48042
+ invalidRootPolicy: "error"
48043
+ });
48044
+ } catch (error) {
48045
+ throw new Error(`Failed to parse Tabnine CLI settings in ${relativePath}: ${formatError(error)}`, { cause: error });
48046
+ }
48047
+ }
48048
+ validate() {
48049
+ return {
48050
+ success: true,
48051
+ error: null
48052
+ };
48053
+ }
48054
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
48055
+ return new TabninePermissions({
48056
+ outputRoot,
48057
+ relativeDirPath,
48058
+ relativeFilePath,
48059
+ fileContent: "{}",
48060
+ validate: false,
48061
+ global
48062
+ });
48063
+ }
48064
+ };
48065
+ //#endregion
45552
48066
  //#region src/features/permissions/takt-permissions.ts
45553
48067
  const TAKT_PROVIDER_KEY = "provider";
45554
48068
  const TAKT_PROVIDER_PROFILES_KEY = "provider_profiles";
@@ -48928,6 +51442,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
48928
51442
  supportsImport: true
48929
51443
  }
48930
51444
  }],
51445
+ ["tabnine", {
51446
+ class: TabninePermissions,
51447
+ meta: {
51448
+ supportsProject: true,
51449
+ supportsGlobal: true,
51450
+ supportsImport: true
51451
+ }
51452
+ }],
48931
51453
  ["takt", {
48932
51454
  class: TaktPermissions,
48933
51455
  meta: {
@@ -50225,16 +52747,198 @@ var AntigravitySharedSkill = class extends ToolSkill {
50225
52747
  }
50226
52748
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
50227
52749
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
50228
- const antigravityFrontmatter = {
52750
+ const antigravityFrontmatter = {
52751
+ name: rulesyncFrontmatter.name,
52752
+ description: rulesyncFrontmatter.description
52753
+ };
52754
+ const settablePaths = this.getSettablePaths({ global });
52755
+ return new this({
52756
+ outputRoot,
52757
+ relativeDirPath: settablePaths.relativeDirPath,
52758
+ dirName: rulesyncSkill.getDirName(),
52759
+ frontmatter: antigravityFrontmatter,
52760
+ body: rulesyncSkill.getBody(),
52761
+ otherFiles: rulesyncSkill.getOtherFiles(),
52762
+ validate,
52763
+ global
52764
+ });
52765
+ }
52766
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
52767
+ const targets = rulesyncSkill.getFrontmatter().targets;
52768
+ return targets.includes("*") || targets.includes(this.getToolTarget());
52769
+ }
52770
+ static async fromDir(params) {
52771
+ const loaded = await this.loadSkillDirContent({
52772
+ ...params,
52773
+ getSettablePaths: (options) => this.getSettablePaths(options)
52774
+ });
52775
+ const result = AntigravitySkillFrontmatterSchema.safeParse(loaded.frontmatter);
52776
+ if (!result.success) {
52777
+ const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
52778
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
52779
+ }
52780
+ return new this({
52781
+ outputRoot: loaded.outputRoot,
52782
+ relativeDirPath: loaded.relativeDirPath,
52783
+ dirName: loaded.dirName,
52784
+ frontmatter: result.data,
52785
+ body: loaded.body,
52786
+ otherFiles: loaded.otherFiles,
52787
+ validate: true,
52788
+ global: loaded.global
52789
+ });
52790
+ }
52791
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
52792
+ return new this({
52793
+ outputRoot,
52794
+ relativeDirPath,
52795
+ dirName,
52796
+ frontmatter: {
52797
+ name: "",
52798
+ description: ""
52799
+ },
52800
+ body: "",
52801
+ otherFiles: [],
52802
+ validate: false,
52803
+ global
52804
+ });
52805
+ }
52806
+ };
52807
+ //#endregion
52808
+ //#region src/features/skills/antigravity-cli-skill.ts
52809
+ /**
52810
+ * Represents a Google Antigravity CLI skill directory (Antigravity 2.0).
52811
+ *
52812
+ * Shares all behavior with {@link AntigravitySharedSkill}; the CLI keeps its
52813
+ * own global skills tree at `~/.gemini/antigravity-cli/skills/` and answers to
52814
+ * the `antigravity-cli` target.
52815
+ */
52816
+ var AntigravityCliSkill = class extends AntigravitySharedSkill {
52817
+ static getGlobalSubdir() {
52818
+ return ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR;
52819
+ }
52820
+ static getToolTarget() {
52821
+ return "antigravity-cli";
52822
+ }
52823
+ };
52824
+ //#endregion
52825
+ //#region src/features/skills/antigravity-ide-skill.ts
52826
+ /**
52827
+ * Represents a Google Antigravity IDE skill directory (Antigravity 2.0).
52828
+ *
52829
+ * Shares all behavior with {@link AntigravitySharedSkill}. As of Antigravity
52830
+ * 2.0 the IDE reads its global skills from the shared `~/.gemini/config/skills/`
52831
+ * location (the Skills doc lists `~/.gemini/config/skills/<skill-folder>/` as
52832
+ * the global scope); the legacy `~/.gemini/antigravity/skills/` path is no
52833
+ * longer used. It answers to the `antigravity-ide` target.
52834
+ */
52835
+ var AntigravityIdeSkill = class extends AntigravitySharedSkill {
52836
+ static getGlobalSubdir() {
52837
+ return ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR;
52838
+ }
52839
+ static getToolTarget() {
52840
+ return "antigravity-ide";
52841
+ }
52842
+ };
52843
+ //#endregion
52844
+ //#region src/features/skills/antigravity-plugin-skill.ts
52845
+ var AntigravityPluginSkill = class extends AntigravityIdeSkill {
52846
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
52847
+ const targets = rulesyncSkill.getFrontmatter().targets;
52848
+ return targets.includes("*") || targets.includes("antigravity-plugin");
52849
+ }
52850
+ static getSettablePaths() {
52851
+ return { relativeDirPath: ANTIGRAVITY_PLUGIN_SKILLS_DIR };
52852
+ }
52853
+ };
52854
+ //#endregion
52855
+ //#region src/features/skills/augmentcode-skill.ts
52856
+ const AugmentcodeSkillFrontmatterSchema = z.looseObject({
52857
+ name: z.string(),
52858
+ description: z.string()
52859
+ });
52860
+ /**
52861
+ * Represents an AugmentCode (Auggie CLI) skill directory.
52862
+ * Auggie has native skill support — it reads `<name>/SKILL.md` directories under
52863
+ * `.augment/skills/` (project) and `~/.augment/skills/` (global).
52864
+ * See https://docs.augmentcode.com/cli/skills
52865
+ */
52866
+ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
52867
+ constructor({ outputRoot = process.cwd(), relativeDirPath = AugmentcodeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
52868
+ super({
52869
+ outputRoot,
52870
+ relativeDirPath,
52871
+ dirName,
52872
+ mainFile: {
52873
+ name: SKILL_FILE_NAME,
52874
+ body,
52875
+ frontmatter: { ...frontmatter }
52876
+ },
52877
+ otherFiles,
52878
+ global
52879
+ });
52880
+ if (validate) {
52881
+ const result = this.validate();
52882
+ if (!result.success) throw result.error;
52883
+ }
52884
+ }
52885
+ static getSettablePaths({ global: _global = false } = {}) {
52886
+ return {
52887
+ relativeDirPath: AUGMENTCODE_SKILLS_DIR_PATH,
52888
+ alternativeSkillRoots: [AUGMENTCODE_AGENTS_SKILLS_DIR_PATH]
52889
+ };
52890
+ }
52891
+ getFrontmatter() {
52892
+ return AugmentcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
52893
+ }
52894
+ getBody() {
52895
+ return this.mainFile?.body ?? "";
52896
+ }
52897
+ validate() {
52898
+ if (this.mainFile === void 0) return {
52899
+ success: false,
52900
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
52901
+ };
52902
+ const result = AugmentcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
52903
+ if (!result.success) return {
52904
+ success: false,
52905
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
52906
+ };
52907
+ return {
52908
+ success: true,
52909
+ error: null
52910
+ };
52911
+ }
52912
+ toRulesyncSkill() {
52913
+ const frontmatter = this.getFrontmatter();
52914
+ const rulesyncFrontmatter = {
52915
+ name: frontmatter.name,
52916
+ description: frontmatter.description,
52917
+ targets: ["*"]
52918
+ };
52919
+ return new RulesyncSkill({
52920
+ outputRoot: this.outputRoot,
52921
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
52922
+ dirName: this.getDirName(),
52923
+ frontmatter: rulesyncFrontmatter,
52924
+ body: this.getBody(),
52925
+ otherFiles: this.getOtherFiles(),
52926
+ validate: true,
52927
+ global: this.global
52928
+ });
52929
+ }
52930
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
52931
+ const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
52932
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
52933
+ const augmentcodeFrontmatter = {
50229
52934
  name: rulesyncFrontmatter.name,
50230
52935
  description: rulesyncFrontmatter.description
50231
52936
  };
50232
- const settablePaths = this.getSettablePaths({ global });
50233
- return new this({
52937
+ return new AugmentcodeSkill({
50234
52938
  outputRoot,
50235
52939
  relativeDirPath: settablePaths.relativeDirPath,
50236
52940
  dirName: rulesyncSkill.getDirName(),
50237
- frontmatter: antigravityFrontmatter,
52941
+ frontmatter: augmentcodeFrontmatter,
50238
52942
  body: rulesyncSkill.getBody(),
50239
52943
  otherFiles: rulesyncSkill.getOtherFiles(),
50240
52944
  validate,
@@ -50243,19 +52947,19 @@ var AntigravitySharedSkill = class extends ToolSkill {
50243
52947
  }
50244
52948
  static isTargetedByRulesyncSkill(rulesyncSkill) {
50245
52949
  const targets = rulesyncSkill.getFrontmatter().targets;
50246
- return targets.includes("*") || targets.includes(this.getToolTarget());
52950
+ return targets.includes("*") || targets.includes("augmentcode");
50247
52951
  }
50248
52952
  static async fromDir(params) {
50249
52953
  const loaded = await this.loadSkillDirContent({
50250
52954
  ...params,
50251
- getSettablePaths: (options) => this.getSettablePaths(options)
52955
+ getSettablePaths: AugmentcodeSkill.getSettablePaths
50252
52956
  });
50253
- const result = AntigravitySkillFrontmatterSchema.safeParse(loaded.frontmatter);
52957
+ const result = AugmentcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
50254
52958
  if (!result.success) {
50255
52959
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
50256
52960
  throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
50257
52961
  }
50258
- return new this({
52962
+ return new AugmentcodeSkill({
50259
52963
  outputRoot: loaded.outputRoot,
50260
52964
  relativeDirPath: loaded.relativeDirPath,
50261
52965
  dirName: loaded.dirName,
@@ -50267,9 +52971,10 @@ var AntigravitySharedSkill = class extends ToolSkill {
50267
52971
  });
50268
52972
  }
50269
52973
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
50270
- return new this({
52974
+ const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
52975
+ return new AugmentcodeSkill({
50271
52976
  outputRoot,
50272
- relativeDirPath,
52977
+ relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
50273
52978
  dirName,
50274
52979
  frontmatter: {
50275
52980
  name: "",
@@ -50283,66 +52988,24 @@ var AntigravitySharedSkill = class extends ToolSkill {
50283
52988
  }
50284
52989
  };
50285
52990
  //#endregion
50286
- //#region src/features/skills/antigravity-cli-skill.ts
50287
- /**
50288
- * Represents a Google Antigravity CLI skill directory (Antigravity 2.0).
50289
- *
50290
- * Shares all behavior with {@link AntigravitySharedSkill}; the CLI keeps its
50291
- * own global skills tree at `~/.gemini/antigravity-cli/skills/` and answers to
50292
- * the `antigravity-cli` target.
50293
- */
50294
- var AntigravityCliSkill = class extends AntigravitySharedSkill {
50295
- static getGlobalSubdir() {
50296
- return ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR;
50297
- }
50298
- static getToolTarget() {
50299
- return "antigravity-cli";
50300
- }
50301
- };
50302
- //#endregion
50303
- //#region src/features/skills/antigravity-ide-skill.ts
50304
- /**
50305
- * Represents a Google Antigravity IDE skill directory (Antigravity 2.0).
50306
- *
50307
- * Shares all behavior with {@link AntigravitySharedSkill}. As of Antigravity
50308
- * 2.0 the IDE reads its global skills from the shared `~/.gemini/config/skills/`
50309
- * location (the Skills doc lists `~/.gemini/config/skills/<skill-folder>/` as
50310
- * the global scope); the legacy `~/.gemini/antigravity/skills/` path is no
50311
- * longer used. It answers to the `antigravity-ide` target.
50312
- */
50313
- var AntigravityIdeSkill = class extends AntigravitySharedSkill {
50314
- static getGlobalSubdir() {
50315
- return ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR;
50316
- }
50317
- static getToolTarget() {
50318
- return "antigravity-ide";
50319
- }
50320
- };
50321
- //#endregion
50322
- //#region src/features/skills/antigravity-plugin-skill.ts
50323
- var AntigravityPluginSkill = class extends AntigravityIdeSkill {
50324
- static isTargetedByRulesyncSkill(rulesyncSkill) {
50325
- const targets = rulesyncSkill.getFrontmatter().targets;
50326
- return targets.includes("*") || targets.includes("antigravity-plugin");
50327
- }
50328
- static getSettablePaths() {
50329
- return { relativeDirPath: ANTIGRAVITY_PLUGIN_SKILLS_DIR };
50330
- }
50331
- };
50332
- //#endregion
50333
- //#region src/features/skills/augmentcode-skill.ts
50334
- const AugmentcodeSkillFrontmatterSchema = z.looseObject({
52991
+ //#region src/features/skills/bob-skill.ts
52992
+ const BobSkillFrontmatterSchema = z.looseObject({
50335
52993
  name: z.string(),
50336
52994
  description: z.string()
50337
52995
  });
50338
52996
  /**
50339
- * Represents an AugmentCode (Auggie CLI) skill directory.
50340
- * Auggie has native skill support — it reads `<name>/SKILL.md` directories under
50341
- * `.augment/skills/` (project) and `~/.augment/skills/` (global).
50342
- * See https://docs.augmentcode.com/cli/skills
52997
+ * Represents an IBM Bob skill directory.
52998
+ *
52999
+ * Bob discovers Anthropic-style `<name>/SKILL.md` directories under
53000
+ * `<project>/.bob/skills/` (project scope) and `~/.bob/skills/` (user scope);
53001
+ * the project copy wins on a name clash. The frontmatter requires `name`
53002
+ * (matching the directory name) and `description`; supporting files next to
53003
+ * `SKILL.md` are carried along.
53004
+ *
53005
+ * @see https://bob.ibm.com/docs/ide/features/skills
50343
53006
  */
50344
- var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50345
- constructor({ outputRoot = process.cwd(), relativeDirPath = AugmentcodeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
53007
+ var BobSkill = class BobSkill extends ToolSkill {
53008
+ constructor({ outputRoot = process.cwd(), relativeDirPath = BOB_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
50346
53009
  super({
50347
53010
  outputRoot,
50348
53011
  relativeDirPath,
@@ -50361,38 +53024,40 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50361
53024
  }
50362
53025
  }
50363
53026
  static getSettablePaths({ global: _global = false } = {}) {
50364
- return {
50365
- relativeDirPath: AUGMENTCODE_SKILLS_DIR_PATH,
50366
- alternativeSkillRoots: [AUGMENTCODE_AGENTS_SKILLS_DIR_PATH]
50367
- };
53027
+ return { relativeDirPath: BOB_SKILLS_DIR_PATH };
50368
53028
  }
50369
53029
  getFrontmatter() {
50370
- return AugmentcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
53030
+ return BobSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
50371
53031
  }
50372
53032
  getBody() {
50373
53033
  return this.mainFile?.body ?? "";
50374
53034
  }
50375
53035
  validate() {
50376
- if (this.mainFile === void 0) return {
53036
+ if (!this.mainFile) return {
50377
53037
  success: false,
50378
53038
  error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
50379
53039
  };
50380
- const result = AugmentcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
53040
+ const result = BobSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
50381
53041
  if (!result.success) return {
50382
53042
  success: false,
50383
53043
  error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
50384
53044
  };
53045
+ if (result.data.name !== this.getDirName()) return {
53046
+ success: false,
53047
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
53048
+ };
50385
53049
  return {
50386
53050
  success: true,
50387
53051
  error: null
50388
53052
  };
50389
53053
  }
50390
53054
  toRulesyncSkill() {
50391
- const frontmatter = this.getFrontmatter();
53055
+ const { name, description, ...bobSection } = this.getFrontmatter();
50392
53056
  const rulesyncFrontmatter = {
50393
- name: frontmatter.name,
50394
- description: frontmatter.description,
50395
- targets: ["*"]
53057
+ name,
53058
+ description,
53059
+ targets: ["*"],
53060
+ ...Object.keys(bobSection).length > 0 && { bob: bobSection }
50396
53061
  };
50397
53062
  return new RulesyncSkill({
50398
53063
  outputRoot: this.outputRoot,
@@ -50406,17 +53071,19 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50406
53071
  });
50407
53072
  }
50408
53073
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
50409
- const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
53074
+ const settablePaths = BobSkill.getSettablePaths({ global });
50410
53075
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
50411
- const augmentcodeFrontmatter = {
53076
+ const { name: _sectionName, description: _sectionDescription, ...bobSection } = rulesyncFrontmatter.bob ?? {};
53077
+ const bobFrontmatter = {
53078
+ ...bobSection,
50412
53079
  name: rulesyncFrontmatter.name,
50413
53080
  description: rulesyncFrontmatter.description
50414
53081
  };
50415
- return new AugmentcodeSkill({
53082
+ return new BobSkill({
50416
53083
  outputRoot,
50417
53084
  relativeDirPath: settablePaths.relativeDirPath,
50418
- dirName: rulesyncSkill.getDirName(),
50419
- frontmatter: augmentcodeFrontmatter,
53085
+ dirName: bobFrontmatter.name,
53086
+ frontmatter: bobFrontmatter,
50420
53087
  body: rulesyncSkill.getBody(),
50421
53088
  otherFiles: rulesyncSkill.getOtherFiles(),
50422
53089
  validate,
@@ -50425,19 +53092,23 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50425
53092
  }
50426
53093
  static isTargetedByRulesyncSkill(rulesyncSkill) {
50427
53094
  const targets = rulesyncSkill.getFrontmatter().targets;
50428
- return targets.includes("*") || targets.includes("augmentcode");
53095
+ return targets.includes("*") || targets.includes("bob");
50429
53096
  }
50430
53097
  static async fromDir(params) {
50431
53098
  const loaded = await this.loadSkillDirContent({
50432
53099
  ...params,
50433
- getSettablePaths: AugmentcodeSkill.getSettablePaths
53100
+ getSettablePaths: BobSkill.getSettablePaths
50434
53101
  });
50435
- const result = AugmentcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
53102
+ const result = BobSkillFrontmatterSchema.safeParse(loaded.frontmatter);
50436
53103
  if (!result.success) {
50437
53104
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
50438
53105
  throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
50439
53106
  }
50440
- return new AugmentcodeSkill({
53107
+ if (result.data.name !== loaded.dirName) {
53108
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
53109
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
53110
+ }
53111
+ return new BobSkill({
50441
53112
  outputRoot: loaded.outputRoot,
50442
53113
  relativeDirPath: loaded.relativeDirPath,
50443
53114
  dirName: loaded.dirName,
@@ -50449,10 +53120,9 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50449
53120
  });
50450
53121
  }
50451
53122
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
50452
- const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
50453
- return new AugmentcodeSkill({
53123
+ return new BobSkill({
50454
53124
  outputRoot,
50455
- relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
53125
+ relativeDirPath,
50456
53126
  dirName,
50457
53127
  frontmatter: {
50458
53128
  name: "",
@@ -51397,7 +54067,167 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51397
54067
  return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
51398
54068
  }
51399
54069
  getFrontmatter() {
51400
- return CopilotSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
54070
+ return CopilotSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
54071
+ }
54072
+ getBody() {
54073
+ return this.mainFile?.body ?? "";
54074
+ }
54075
+ validate() {
54076
+ if (!this.mainFile) return {
54077
+ success: false,
54078
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
54079
+ };
54080
+ const result = CopilotSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
54081
+ if (!result.success) return {
54082
+ success: false,
54083
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
54084
+ };
54085
+ return {
54086
+ success: true,
54087
+ error: null
54088
+ };
54089
+ }
54090
+ toRulesyncSkill() {
54091
+ const { name, description, ...copilotSection } = this.getFrontmatter();
54092
+ const rulesyncFrontmatter = {
54093
+ name,
54094
+ description,
54095
+ targets: ["*"],
54096
+ ...Object.keys(copilotSection).length > 0 && { copilot: copilotSection }
54097
+ };
54098
+ return new RulesyncSkill({
54099
+ outputRoot: this.outputRoot,
54100
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
54101
+ dirName: this.getDirName(),
54102
+ frontmatter: rulesyncFrontmatter,
54103
+ body: this.getBody(),
54104
+ otherFiles: this.getOtherFiles(),
54105
+ validate: true,
54106
+ global: this.global
54107
+ });
54108
+ }
54109
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
54110
+ const settablePaths = CopilotSkill.getSettablePaths({ global });
54111
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
54112
+ const copilotSection = rulesyncFrontmatter.copilot;
54113
+ const resolvedUserInvocable = resolveUserInvocable({
54114
+ rootFrontmatter: rulesyncFrontmatter,
54115
+ section: copilotSection
54116
+ });
54117
+ const resolvedDisableModelInvocation = resolveDisableModelInvocation({
54118
+ rootFrontmatter: rulesyncFrontmatter,
54119
+ section: copilotSection
54120
+ });
54121
+ const license = resolveLicense({
54122
+ rootFrontmatter: rulesyncFrontmatter,
54123
+ section: copilotSection
54124
+ });
54125
+ const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotFields } = copilotSection ?? {};
54126
+ const copilotFrontmatter = {
54127
+ ...copilotFields,
54128
+ name: rulesyncFrontmatter.name,
54129
+ description: rulesyncFrontmatter.description,
54130
+ ...license !== void 0 && { license },
54131
+ ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
54132
+ ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
54133
+ };
54134
+ return new CopilotSkill({
54135
+ outputRoot,
54136
+ relativeDirPath: settablePaths.relativeDirPath,
54137
+ dirName: rulesyncSkill.getDirName(),
54138
+ frontmatter: copilotFrontmatter,
54139
+ body: rulesyncSkill.getBody(),
54140
+ otherFiles: rulesyncSkill.getOtherFiles(),
54141
+ validate,
54142
+ global
54143
+ });
54144
+ }
54145
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
54146
+ const targets = rulesyncSkill.getFrontmatter().targets;
54147
+ return targets.includes("*") || targets.includes("copilot");
54148
+ }
54149
+ static async fromDir(params) {
54150
+ const loaded = await this.loadSkillDirContent({
54151
+ ...params,
54152
+ getSettablePaths: CopilotSkill.getSettablePaths
54153
+ });
54154
+ const result = CopilotSkillFrontmatterSchema.safeParse(loaded.frontmatter);
54155
+ if (!result.success) {
54156
+ const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
54157
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
54158
+ }
54159
+ return new CopilotSkill({
54160
+ outputRoot: loaded.outputRoot,
54161
+ relativeDirPath: loaded.relativeDirPath,
54162
+ dirName: loaded.dirName,
54163
+ frontmatter: result.data,
54164
+ body: loaded.body,
54165
+ otherFiles: loaded.otherFiles,
54166
+ validate: true,
54167
+ global: loaded.global
54168
+ });
54169
+ }
54170
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
54171
+ const settablePaths = CopilotSkill.getSettablePaths({ global });
54172
+ return new CopilotSkill({
54173
+ outputRoot,
54174
+ relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
54175
+ dirName,
54176
+ frontmatter: {
54177
+ name: "",
54178
+ description: ""
54179
+ },
54180
+ body: "",
54181
+ otherFiles: [],
54182
+ validate: false,
54183
+ global
54184
+ });
54185
+ }
54186
+ };
54187
+ //#endregion
54188
+ //#region src/features/skills/copilotcli-skill.ts
54189
+ const CopilotcliSkillFrontmatterSchema = z.looseObject({
54190
+ name: z.string(),
54191
+ description: z.string(),
54192
+ license: z.optional(z.string()),
54193
+ "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
54194
+ "argument-hint": z.optional(z.string()),
54195
+ "user-invocable": z.optional(z.boolean()),
54196
+ "disable-model-invocation": z.optional(z.boolean())
54197
+ });
54198
+ /**
54199
+ * Represents a GitHub Copilot CLI skill directory.
54200
+ *
54201
+ * Copilot CLI discovers project skills from `.github/skills/` (shared with the
54202
+ * Copilot IDE target) and personal/global skills from `~/.copilot/skills/`. Each
54203
+ * skill is a directory containing a `SKILL.md` file with `name`/`description`
54204
+ * frontmatter. https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills
54205
+ */
54206
+ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
54207
+ constructor({ outputRoot = process.cwd(), relativeDirPath = COPILOT_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
54208
+ super({
54209
+ outputRoot,
54210
+ relativeDirPath,
54211
+ dirName,
54212
+ mainFile: {
54213
+ name: SKILL_FILE_NAME,
54214
+ body,
54215
+ frontmatter: { ...frontmatter }
54216
+ },
54217
+ otherFiles,
54218
+ global
54219
+ });
54220
+ if (validate) {
54221
+ const result = this.validate();
54222
+ if (!result.success) throw result.error;
54223
+ }
54224
+ }
54225
+ static getSettablePaths(options) {
54226
+ if (options?.global) return { relativeDirPath: COPILOT_SKILLS_GLOBAL_DIR_PATH };
54227
+ return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
54228
+ }
54229
+ getFrontmatter() {
54230
+ return CopilotcliSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
51401
54231
  }
51402
54232
  getBody() {
51403
54233
  return this.mainFile?.body ?? "";
@@ -51407,7 +54237,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51407
54237
  success: false,
51408
54238
  error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
51409
54239
  };
51410
- const result = CopilotSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
54240
+ const result = CopilotcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
51411
54241
  if (!result.success) return {
51412
54242
  success: false,
51413
54243
  error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
@@ -51418,12 +54248,12 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51418
54248
  };
51419
54249
  }
51420
54250
  toRulesyncSkill() {
51421
- const { name, description, ...copilotSection } = this.getFrontmatter();
54251
+ const { name, description, ...copilotcliSection } = this.getFrontmatter();
51422
54252
  const rulesyncFrontmatter = {
51423
54253
  name,
51424
54254
  description,
51425
54255
  targets: ["*"],
51426
- ...Object.keys(copilotSection).length > 0 && { copilot: copilotSection }
54256
+ ...Object.keys(copilotcliSection).length > 0 && { copilotcli: copilotcliSection }
51427
54257
  };
51428
54258
  return new RulesyncSkill({
51429
54259
  outputRoot: this.outputRoot,
@@ -51437,35 +54267,35 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51437
54267
  });
51438
54268
  }
51439
54269
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
51440
- const settablePaths = CopilotSkill.getSettablePaths({ global });
54270
+ const settablePaths = CopilotcliSkill.getSettablePaths({ global });
51441
54271
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
51442
- const copilotSection = rulesyncFrontmatter.copilot;
54272
+ const copilotcliSection = rulesyncFrontmatter.copilotcli;
51443
54273
  const resolvedUserInvocable = resolveUserInvocable({
51444
54274
  rootFrontmatter: rulesyncFrontmatter,
51445
- section: copilotSection
54275
+ section: copilotcliSection
51446
54276
  });
51447
54277
  const resolvedDisableModelInvocation = resolveDisableModelInvocation({
51448
54278
  rootFrontmatter: rulesyncFrontmatter,
51449
- section: copilotSection
54279
+ section: copilotcliSection
51450
54280
  });
51451
54281
  const license = resolveLicense({
51452
54282
  rootFrontmatter: rulesyncFrontmatter,
51453
- section: copilotSection
54283
+ section: copilotcliSection
51454
54284
  });
51455
- const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotFields } = copilotSection ?? {};
51456
- const copilotFrontmatter = {
51457
- ...copilotFields,
54285
+ const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotcliFields } = copilotcliSection ?? {};
54286
+ const copilotcliFrontmatter = {
54287
+ ...copilotcliFields,
51458
54288
  name: rulesyncFrontmatter.name,
51459
54289
  description: rulesyncFrontmatter.description,
51460
54290
  ...license !== void 0 && { license },
51461
54291
  ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
51462
54292
  ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
51463
54293
  };
51464
- return new CopilotSkill({
54294
+ return new CopilotcliSkill({
51465
54295
  outputRoot,
51466
54296
  relativeDirPath: settablePaths.relativeDirPath,
51467
54297
  dirName: rulesyncSkill.getDirName(),
51468
- frontmatter: copilotFrontmatter,
54298
+ frontmatter: copilotcliFrontmatter,
51469
54299
  body: rulesyncSkill.getBody(),
51470
54300
  otherFiles: rulesyncSkill.getOtherFiles(),
51471
54301
  validate,
@@ -51474,19 +54304,19 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51474
54304
  }
51475
54305
  static isTargetedByRulesyncSkill(rulesyncSkill) {
51476
54306
  const targets = rulesyncSkill.getFrontmatter().targets;
51477
- return targets.includes("*") || targets.includes("copilot");
54307
+ return targets.includes("*") || targets.includes("copilotcli");
51478
54308
  }
51479
54309
  static async fromDir(params) {
51480
54310
  const loaded = await this.loadSkillDirContent({
51481
54311
  ...params,
51482
- getSettablePaths: CopilotSkill.getSettablePaths
54312
+ getSettablePaths: CopilotcliSkill.getSettablePaths
51483
54313
  });
51484
- const result = CopilotSkillFrontmatterSchema.safeParse(loaded.frontmatter);
54314
+ const result = CopilotcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
51485
54315
  if (!result.success) {
51486
54316
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
51487
54317
  throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
51488
54318
  }
51489
- return new CopilotSkill({
54319
+ return new CopilotcliSkill({
51490
54320
  outputRoot: loaded.outputRoot,
51491
54321
  relativeDirPath: loaded.relativeDirPath,
51492
54322
  dirName: loaded.dirName,
@@ -51498,8 +54328,8 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51498
54328
  });
51499
54329
  }
51500
54330
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
51501
- const settablePaths = CopilotSkill.getSettablePaths({ global });
51502
- return new CopilotSkill({
54331
+ const settablePaths = CopilotcliSkill.getSettablePaths({ global });
54332
+ return new CopilotcliSkill({
51503
54333
  outputRoot,
51504
54334
  relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
51505
54335
  dirName,
@@ -51515,26 +54345,25 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51515
54345
  }
51516
54346
  };
51517
54347
  //#endregion
51518
- //#region src/features/skills/copilotcli-skill.ts
51519
- const CopilotcliSkillFrontmatterSchema = z.looseObject({
54348
+ //#region src/features/skills/cortexcode-skill.ts
54349
+ const CortexcodeSkillFrontmatterSchema = z.looseObject({
51520
54350
  name: z.string(),
51521
54351
  description: z.string(),
51522
- license: z.optional(z.string()),
51523
- "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
51524
- "argument-hint": z.optional(z.string()),
51525
- "user-invocable": z.optional(z.boolean()),
51526
- "disable-model-invocation": z.optional(z.boolean())
54352
+ /** Tool names the skill may use; omitted = every tool. */
54353
+ tools: z.optional(z.array(z.string()))
51527
54354
  });
51528
54355
  /**
51529
- * Represents a GitHub Copilot CLI skill directory.
54356
+ * Represents a Snowflake Cortex Code skill directory.
51530
54357
  *
51531
- * Copilot CLI discovers project skills from `.github/skills/` (shared with the
51532
- * Copilot IDE target) and personal/global skills from `~/.copilot/skills/`. Each
51533
- * skill is a directory containing a `SKILL.md` file with `name`/`description`
51534
- * frontmatter. https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills
54358
+ * Cortex Code discovers Anthropic-style `<name>/SKILL.md` directories under
54359
+ * `<project>/.cortex/skills/` (project scope) and `~/.snowflake/cortex/skills/`
54360
+ * (user scope). The frontmatter requires `name` (matching the directory name)
54361
+ * and `description`; supporting files next to `SKILL.md` are carried along.
54362
+ *
54363
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility
51535
54364
  */
51536
- var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51537
- constructor({ outputRoot = process.cwd(), relativeDirPath = COPILOT_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
54365
+ var CortexcodeSkill = class CortexcodeSkill extends ToolSkill {
54366
+ constructor({ outputRoot = process.cwd(), relativeDirPath = CORTEXCODE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
51538
54367
  super({
51539
54368
  outputRoot,
51540
54369
  relativeDirPath,
@@ -51552,12 +54381,11 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51552
54381
  if (!result.success) throw result.error;
51553
54382
  }
51554
54383
  }
51555
- static getSettablePaths(options) {
51556
- if (options?.global) return { relativeDirPath: COPILOT_SKILLS_GLOBAL_DIR_PATH };
51557
- return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
54384
+ static getSettablePaths({ global = false } = {}) {
54385
+ return { relativeDirPath: global ? CORTEXCODE_GLOBAL_SKILLS_DIR_PATH : CORTEXCODE_SKILLS_DIR_PATH };
51558
54386
  }
51559
54387
  getFrontmatter() {
51560
- return CopilotcliSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
54388
+ return CortexcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
51561
54389
  }
51562
54390
  getBody() {
51563
54391
  return this.mainFile?.body ?? "";
@@ -51567,23 +54395,27 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51567
54395
  success: false,
51568
54396
  error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
51569
54397
  };
51570
- const result = CopilotcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
54398
+ const result = CortexcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
51571
54399
  if (!result.success) return {
51572
54400
  success: false,
51573
54401
  error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
51574
54402
  };
54403
+ if (result.data.name !== this.getDirName()) return {
54404
+ success: false,
54405
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
54406
+ };
51575
54407
  return {
51576
54408
  success: true,
51577
54409
  error: null
51578
54410
  };
51579
54411
  }
51580
54412
  toRulesyncSkill() {
51581
- const { name, description, ...copilotcliSection } = this.getFrontmatter();
54413
+ const { name, description, ...cortexcodeSection } = this.getFrontmatter();
51582
54414
  const rulesyncFrontmatter = {
51583
54415
  name,
51584
54416
  description,
51585
54417
  targets: ["*"],
51586
- ...Object.keys(copilotcliSection).length > 0 && { copilotcli: copilotcliSection }
54418
+ ...Object.keys(cortexcodeSection).length > 0 && { cortexcode: cortexcodeSection }
51587
54419
  };
51588
54420
  return new RulesyncSkill({
51589
54421
  outputRoot: this.outputRoot,
@@ -51597,35 +54429,19 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51597
54429
  });
51598
54430
  }
51599
54431
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
51600
- const settablePaths = CopilotcliSkill.getSettablePaths({ global });
54432
+ const settablePaths = CortexcodeSkill.getSettablePaths({ global });
51601
54433
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
51602
- const copilotcliSection = rulesyncFrontmatter.copilotcli;
51603
- const resolvedUserInvocable = resolveUserInvocable({
51604
- rootFrontmatter: rulesyncFrontmatter,
51605
- section: copilotcliSection
51606
- });
51607
- const resolvedDisableModelInvocation = resolveDisableModelInvocation({
51608
- rootFrontmatter: rulesyncFrontmatter,
51609
- section: copilotcliSection
51610
- });
51611
- const license = resolveLicense({
51612
- rootFrontmatter: rulesyncFrontmatter,
51613
- section: copilotcliSection
51614
- });
51615
- const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotcliFields } = copilotcliSection ?? {};
51616
- const copilotcliFrontmatter = {
51617
- ...copilotcliFields,
54434
+ const { name: _sectionName, description: _sectionDescription, ...cortexcodeSection } = rulesyncFrontmatter.cortexcode ?? {};
54435
+ const cortexcodeFrontmatter = {
54436
+ ...cortexcodeSection,
51618
54437
  name: rulesyncFrontmatter.name,
51619
- description: rulesyncFrontmatter.description,
51620
- ...license !== void 0 && { license },
51621
- ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
51622
- ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
54438
+ description: rulesyncFrontmatter.description
51623
54439
  };
51624
- return new CopilotcliSkill({
54440
+ return new CortexcodeSkill({
51625
54441
  outputRoot,
51626
54442
  relativeDirPath: settablePaths.relativeDirPath,
51627
- dirName: rulesyncSkill.getDirName(),
51628
- frontmatter: copilotcliFrontmatter,
54443
+ dirName: cortexcodeFrontmatter.name,
54444
+ frontmatter: cortexcodeFrontmatter,
51629
54445
  body: rulesyncSkill.getBody(),
51630
54446
  otherFiles: rulesyncSkill.getOtherFiles(),
51631
54447
  validate,
@@ -51634,19 +54450,23 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51634
54450
  }
51635
54451
  static isTargetedByRulesyncSkill(rulesyncSkill) {
51636
54452
  const targets = rulesyncSkill.getFrontmatter().targets;
51637
- return targets.includes("*") || targets.includes("copilotcli");
54453
+ return targets.includes("*") || targets.includes("cortexcode");
51638
54454
  }
51639
54455
  static async fromDir(params) {
51640
54456
  const loaded = await this.loadSkillDirContent({
51641
54457
  ...params,
51642
- getSettablePaths: CopilotcliSkill.getSettablePaths
54458
+ getSettablePaths: CortexcodeSkill.getSettablePaths
51643
54459
  });
51644
- const result = CopilotcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
54460
+ const result = CortexcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
51645
54461
  if (!result.success) {
51646
54462
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
51647
54463
  throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
51648
54464
  }
51649
- return new CopilotcliSkill({
54465
+ if (result.data.name !== loaded.dirName) {
54466
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
54467
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
54468
+ }
54469
+ return new CortexcodeSkill({
51650
54470
  outputRoot: loaded.outputRoot,
51651
54471
  relativeDirPath: loaded.relativeDirPath,
51652
54472
  dirName: loaded.dirName,
@@ -51658,10 +54478,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51658
54478
  });
51659
54479
  }
51660
54480
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
51661
- const settablePaths = CopilotcliSkill.getSettablePaths({ global });
51662
- return new CopilotcliSkill({
54481
+ return new CortexcodeSkill({
51663
54482
  outputRoot,
51664
- relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
54483
+ relativeDirPath,
51665
54484
  dirName,
51666
54485
  frontmatter: {
51667
54486
  name: "",
@@ -54985,6 +57804,154 @@ var RooSkill = class RooSkill extends ToolSkill {
54985
57804
  }
54986
57805
  };
54987
57806
  //#endregion
57807
+ //#region src/features/skills/tabnine-skill.ts
57808
+ const TabnineSkillFrontmatterSchema = z.looseObject({
57809
+ name: z.string(),
57810
+ description: z.string()
57811
+ });
57812
+ /**
57813
+ * Represents a Tabnine CLI skill directory.
57814
+ *
57815
+ * Tabnine CLI discovers Anthropic-style `<name>/SKILL.md` directories under
57816
+ * `<project>/.tabnine/agent/skills/` (workspace scope) and
57817
+ * `~/.tabnine/agent/skills/` (user scope); the workspace copy wins on a name
57818
+ * clash. The frontmatter requires `name` (matching the directory name) and
57819
+ * `description`; supporting files next to `SKILL.md` are carried along.
57820
+ *
57821
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/agent-skills
57822
+ */
57823
+ var TabnineSkill = class TabnineSkill extends ToolSkill {
57824
+ constructor({ outputRoot = process.cwd(), relativeDirPath = TABNINE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
57825
+ super({
57826
+ outputRoot,
57827
+ relativeDirPath,
57828
+ dirName,
57829
+ mainFile: {
57830
+ name: SKILL_FILE_NAME,
57831
+ body,
57832
+ frontmatter: { ...frontmatter }
57833
+ },
57834
+ otherFiles,
57835
+ global
57836
+ });
57837
+ if (validate) {
57838
+ const result = this.validate();
57839
+ if (!result.success) throw result.error;
57840
+ }
57841
+ }
57842
+ static getSettablePaths({ global: _global = false } = {}) {
57843
+ return { relativeDirPath: TABNINE_SKILLS_DIR_PATH };
57844
+ }
57845
+ getFrontmatter() {
57846
+ return TabnineSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
57847
+ }
57848
+ getBody() {
57849
+ return this.mainFile?.body ?? "";
57850
+ }
57851
+ validate() {
57852
+ if (!this.mainFile) return {
57853
+ success: false,
57854
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
57855
+ };
57856
+ const result = TabnineSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
57857
+ if (!result.success) return {
57858
+ success: false,
57859
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
57860
+ };
57861
+ if (result.data.name !== this.getDirName()) return {
57862
+ success: false,
57863
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
57864
+ };
57865
+ return {
57866
+ success: true,
57867
+ error: null
57868
+ };
57869
+ }
57870
+ toRulesyncSkill() {
57871
+ const { name, description, ...tabnineSection } = this.getFrontmatter();
57872
+ const rulesyncFrontmatter = {
57873
+ name,
57874
+ description,
57875
+ targets: ["*"],
57876
+ ...Object.keys(tabnineSection).length > 0 && { tabnine: tabnineSection }
57877
+ };
57878
+ return new RulesyncSkill({
57879
+ outputRoot: this.outputRoot,
57880
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
57881
+ dirName: this.getDirName(),
57882
+ frontmatter: rulesyncFrontmatter,
57883
+ body: this.getBody(),
57884
+ otherFiles: this.getOtherFiles(),
57885
+ validate: true,
57886
+ global: this.global
57887
+ });
57888
+ }
57889
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
57890
+ const settablePaths = TabnineSkill.getSettablePaths({ global });
57891
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
57892
+ const { name: _sectionName, description: _sectionDescription, ...tabnineSection } = rulesyncFrontmatter.tabnine ?? {};
57893
+ const tabnineFrontmatter = {
57894
+ ...tabnineSection,
57895
+ name: rulesyncFrontmatter.name,
57896
+ description: rulesyncFrontmatter.description
57897
+ };
57898
+ return new TabnineSkill({
57899
+ outputRoot,
57900
+ relativeDirPath: settablePaths.relativeDirPath,
57901
+ dirName: tabnineFrontmatter.name,
57902
+ frontmatter: tabnineFrontmatter,
57903
+ body: rulesyncSkill.getBody(),
57904
+ otherFiles: rulesyncSkill.getOtherFiles(),
57905
+ validate,
57906
+ global
57907
+ });
57908
+ }
57909
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
57910
+ const targets = rulesyncSkill.getFrontmatter().targets;
57911
+ return targets.includes("*") || targets.includes("tabnine");
57912
+ }
57913
+ static async fromDir(params) {
57914
+ const loaded = await this.loadSkillDirContent({
57915
+ ...params,
57916
+ getSettablePaths: TabnineSkill.getSettablePaths
57917
+ });
57918
+ const result = TabnineSkillFrontmatterSchema.safeParse(loaded.frontmatter);
57919
+ if (!result.success) {
57920
+ const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
57921
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
57922
+ }
57923
+ if (result.data.name !== loaded.dirName) {
57924
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
57925
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
57926
+ }
57927
+ return new TabnineSkill({
57928
+ outputRoot: loaded.outputRoot,
57929
+ relativeDirPath: loaded.relativeDirPath,
57930
+ dirName: loaded.dirName,
57931
+ frontmatter: result.data,
57932
+ body: loaded.body,
57933
+ otherFiles: loaded.otherFiles,
57934
+ validate: true,
57935
+ global: loaded.global
57936
+ });
57937
+ }
57938
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
57939
+ return new TabnineSkill({
57940
+ outputRoot,
57941
+ relativeDirPath,
57942
+ dirName,
57943
+ frontmatter: {
57944
+ name: "",
57945
+ description: ""
57946
+ },
57947
+ body: "",
57948
+ otherFiles: [],
57949
+ validate: false,
57950
+ global
57951
+ });
57952
+ }
57953
+ };
57954
+ //#endregion
54988
57955
  //#region src/features/skills/takt-skill.ts
54989
57956
  /**
54990
57957
  * Skill generator for TAKT.
@@ -55873,6 +58840,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
55873
58840
  supportsGlobal: true
55874
58841
  }
55875
58842
  }],
58843
+ ["bob", {
58844
+ class: BobSkill,
58845
+ meta: {
58846
+ supportsProject: true,
58847
+ supportsSimulated: false,
58848
+ supportsGlobal: true
58849
+ }
58850
+ }],
55876
58851
  ["claudecode", {
55877
58852
  class: ClaudecodeSkill,
55878
58853
  meta: {
@@ -55929,6 +58904,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
55929
58904
  supportsGlobal: true
55930
58905
  }
55931
58906
  }],
58907
+ ["cortexcode", {
58908
+ class: CortexcodeSkill,
58909
+ meta: {
58910
+ supportsProject: true,
58911
+ supportsSimulated: false,
58912
+ supportsGlobal: true
58913
+ }
58914
+ }],
55932
58915
  ["crush", {
55933
58916
  class: CrushSkill,
55934
58917
  meta: {
@@ -56106,6 +59089,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
56106
59089
  supportsGlobal: true
56107
59090
  }
56108
59091
  }],
59092
+ ["tabnine", {
59093
+ class: TabnineSkill,
59094
+ meta: {
59095
+ supportsProject: true,
59096
+ supportsSimulated: false,
59097
+ supportsGlobal: true
59098
+ }
59099
+ }],
56109
59100
  ["takt", {
56110
59101
  class: TaktSkill,
56111
59102
  meta: {
@@ -58300,6 +61291,135 @@ var CopilotcliSubagent = class CopilotcliSubagent extends ToolSubagent {
58300
61291
  }
58301
61292
  };
58302
61293
  //#endregion
61294
+ //#region src/features/subagents/cortexcode-subagent.ts
61295
+ const CortexcodeSubagentFrontmatterSchema = z.looseObject({
61296
+ name: z.string(),
61297
+ description: z.optional(z.string()),
61298
+ /** Tool names the subagent may use; omitted = every tool. */
61299
+ tools: z.optional(z.array(z.string())),
61300
+ model: z.optional(z.string())
61301
+ });
61302
+ /**
61303
+ * Snowflake Cortex Code subagent: a Markdown file with YAML frontmatter under
61304
+ * `.cortex/agents/` (project) or `~/.snowflake/cortex/agents/` (user).
61305
+ */
61306
+ var CortexcodeSubagent = class CortexcodeSubagent extends ToolSubagent {
61307
+ frontmatter;
61308
+ body;
61309
+ constructor({ frontmatter, body, fileContent, ...rest }) {
61310
+ if (rest.validate !== false) {
61311
+ const result = CortexcodeSubagentFrontmatterSchema.safeParse(frontmatter);
61312
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
61313
+ }
61314
+ super({
61315
+ ...rest,
61316
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
61317
+ });
61318
+ this.frontmatter = frontmatter;
61319
+ this.body = body;
61320
+ }
61321
+ static getSettablePaths({ global = false } = {}) {
61322
+ return { relativeDirPath: global ? CORTEXCODE_GLOBAL_AGENTS_DIR_PATH : CORTEXCODE_AGENTS_DIR_PATH };
61323
+ }
61324
+ getFrontmatter() {
61325
+ return this.frontmatter;
61326
+ }
61327
+ getBody() {
61328
+ return this.body;
61329
+ }
61330
+ toRulesyncSubagent() {
61331
+ const { name, description, ...rest } = this.frontmatter;
61332
+ return new RulesyncSubagent({
61333
+ outputRoot: ".",
61334
+ frontmatter: {
61335
+ targets: ["*"],
61336
+ name,
61337
+ description,
61338
+ cortexcode: { ...rest }
61339
+ },
61340
+ body: this.body,
61341
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
61342
+ relativeFilePath: this.getRelativeFilePath(),
61343
+ validate: true
61344
+ });
61345
+ }
61346
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
61347
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
61348
+ const cortexcodeSection = rulesyncFrontmatter.cortexcode ?? {};
61349
+ const cortexcodeSubagentFrontmatter = {
61350
+ name: rulesyncFrontmatter.name,
61351
+ description: rulesyncFrontmatter.description,
61352
+ ...cortexcodeSection
61353
+ };
61354
+ const body = rulesyncSubagent.getBody();
61355
+ const fileContent = stringifyFrontmatter(body, cortexcodeSubagentFrontmatter, { avoidBlockScalars: true });
61356
+ const paths = this.getSettablePaths({ global });
61357
+ return new CortexcodeSubagent({
61358
+ outputRoot,
61359
+ frontmatter: cortexcodeSubagentFrontmatter,
61360
+ body,
61361
+ relativeDirPath: paths.relativeDirPath,
61362
+ relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
61363
+ fileContent,
61364
+ validate,
61365
+ global
61366
+ });
61367
+ }
61368
+ validate() {
61369
+ if (!this.frontmatter) return {
61370
+ success: true,
61371
+ error: null
61372
+ };
61373
+ const result = CortexcodeSubagentFrontmatterSchema.safeParse(this.frontmatter);
61374
+ if (result.success) return {
61375
+ success: true,
61376
+ error: null
61377
+ };
61378
+ else return {
61379
+ success: false,
61380
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
61381
+ };
61382
+ }
61383
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
61384
+ return this.isTargetedByRulesyncSubagentDefault({
61385
+ rulesyncSubagent,
61386
+ toolTarget: "cortexcode"
61387
+ });
61388
+ }
61389
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
61390
+ const paths = this.getSettablePaths({ global });
61391
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
61392
+ const fileContent = await readFileContent(filePath);
61393
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
61394
+ const result = CortexcodeSubagentFrontmatterSchema.safeParse(frontmatter);
61395
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
61396
+ return new CortexcodeSubagent({
61397
+ outputRoot,
61398
+ relativeDirPath: paths.relativeDirPath,
61399
+ relativeFilePath,
61400
+ frontmatter: result.data,
61401
+ body: content.trim(),
61402
+ fileContent,
61403
+ validate,
61404
+ global
61405
+ });
61406
+ }
61407
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
61408
+ return new CortexcodeSubagent({
61409
+ outputRoot,
61410
+ relativeDirPath,
61411
+ relativeFilePath,
61412
+ frontmatter: {
61413
+ name: "",
61414
+ description: ""
61415
+ },
61416
+ body: "",
61417
+ fileContent: "",
61418
+ validate: false
61419
+ });
61420
+ }
61421
+ };
61422
+ //#endregion
58303
61423
  //#region src/features/subagents/cursor-subagent.ts
58304
61424
  const CursorSubagentFrontmatterSchema = z.looseObject({
58305
61425
  name: z.string(),
@@ -60752,6 +63872,145 @@ var RooSubagent = class extends ToolSubagent {
60752
63872
  }
60753
63873
  };
60754
63874
  //#endregion
63875
+ //#region src/features/subagents/tabnine-subagent.ts
63876
+ const TabnineSubagentFrontmatterSchema = z.looseObject({
63877
+ name: z.string(),
63878
+ description: z.optional(z.string()),
63879
+ /** "local" (default) runs inside the CLI; "remote" delegates to a Tabnine cloud agent. */
63880
+ kind: z.optional(z.string()),
63881
+ /** Built-in tool names the subagent may use; omitted = every tool. */
63882
+ tools: z.optional(z.array(z.string())),
63883
+ model: z.optional(z.string()),
63884
+ /** Sampling temperature, 0.0-2.0. */
63885
+ temperature: z.optional(z.number()),
63886
+ /** Maximum agent turns (Tabnine default: 15). */
63887
+ max_turns: z.optional(z.number()),
63888
+ /** Wall-clock budget in minutes (Tabnine default: 5). */
63889
+ timeout_mins: z.optional(z.number())
63890
+ });
63891
+ /**
63892
+ * Tabnine CLI subagent: a Markdown file with YAML frontmatter under
63893
+ * `.tabnine/agent/agents/` (project) or `~/.tabnine/agent/agents/` (user).
63894
+ * Tabnine only loads these when `experimental.enableAgents` is `true` in its
63895
+ * settings; rulesync writes the files and leaves that flag to the user.
63896
+ */
63897
+ var TabnineSubagent = class TabnineSubagent extends ToolSubagent {
63898
+ frontmatter;
63899
+ body;
63900
+ constructor({ frontmatter, body, fileContent, ...rest }) {
63901
+ if (rest.validate !== false) {
63902
+ const result = TabnineSubagentFrontmatterSchema.safeParse(frontmatter);
63903
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
63904
+ }
63905
+ super({
63906
+ ...rest,
63907
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
63908
+ });
63909
+ this.frontmatter = frontmatter;
63910
+ this.body = body;
63911
+ }
63912
+ static getSettablePaths(_options = {}) {
63913
+ return { relativeDirPath: TABNINE_AGENTS_DIR_PATH };
63914
+ }
63915
+ getFrontmatter() {
63916
+ return this.frontmatter;
63917
+ }
63918
+ getBody() {
63919
+ return this.body;
63920
+ }
63921
+ toRulesyncSubagent() {
63922
+ const { name, description, ...rest } = this.frontmatter;
63923
+ return new RulesyncSubagent({
63924
+ outputRoot: ".",
63925
+ frontmatter: {
63926
+ targets: ["*"],
63927
+ name,
63928
+ description,
63929
+ tabnine: { ...rest }
63930
+ },
63931
+ body: this.body,
63932
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
63933
+ relativeFilePath: this.getRelativeFilePath(),
63934
+ validate: true
63935
+ });
63936
+ }
63937
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
63938
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
63939
+ const tabnineSection = rulesyncFrontmatter.tabnine ?? {};
63940
+ const tabnineSubagentFrontmatter = {
63941
+ name: rulesyncFrontmatter.name,
63942
+ description: rulesyncFrontmatter.description,
63943
+ ...tabnineSection
63944
+ };
63945
+ const body = rulesyncSubagent.getBody();
63946
+ const fileContent = stringifyFrontmatter(body, tabnineSubagentFrontmatter, { avoidBlockScalars: true });
63947
+ const paths = this.getSettablePaths({ global });
63948
+ return new TabnineSubagent({
63949
+ outputRoot,
63950
+ frontmatter: tabnineSubagentFrontmatter,
63951
+ body,
63952
+ relativeDirPath: paths.relativeDirPath,
63953
+ relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
63954
+ fileContent,
63955
+ validate,
63956
+ global
63957
+ });
63958
+ }
63959
+ validate() {
63960
+ if (!this.frontmatter) return {
63961
+ success: true,
63962
+ error: null
63963
+ };
63964
+ const result = TabnineSubagentFrontmatterSchema.safeParse(this.frontmatter);
63965
+ if (result.success) return {
63966
+ success: true,
63967
+ error: null
63968
+ };
63969
+ else return {
63970
+ success: false,
63971
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
63972
+ };
63973
+ }
63974
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
63975
+ return this.isTargetedByRulesyncSubagentDefault({
63976
+ rulesyncSubagent,
63977
+ toolTarget: "tabnine"
63978
+ });
63979
+ }
63980
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
63981
+ const paths = this.getSettablePaths({ global });
63982
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
63983
+ const fileContent = await readFileContent(filePath);
63984
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
63985
+ const result = TabnineSubagentFrontmatterSchema.safeParse(frontmatter);
63986
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
63987
+ return new TabnineSubagent({
63988
+ outputRoot,
63989
+ relativeDirPath: paths.relativeDirPath,
63990
+ relativeFilePath,
63991
+ frontmatter: result.data,
63992
+ body: content.trim(),
63993
+ fileContent,
63994
+ validate,
63995
+ global
63996
+ });
63997
+ }
63998
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
63999
+ return new TabnineSubagent({
64000
+ outputRoot,
64001
+ relativeDirPath,
64002
+ relativeFilePath,
64003
+ frontmatter: {
64004
+ name: "",
64005
+ description: ""
64006
+ },
64007
+ body: "",
64008
+ fileContent: "",
64009
+ validate: false
64010
+ });
64011
+ }
64012
+ };
64013
+ //#endregion
60755
64014
  //#region src/features/subagents/takt-subagent.ts
60756
64015
  /**
60757
64016
  * Subagent generator for TAKT.
@@ -61383,6 +64642,15 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
61383
64642
  filePattern: "*.agent.md"
61384
64643
  }
61385
64644
  }],
64645
+ ["cortexcode", {
64646
+ class: CortexcodeSubagent,
64647
+ meta: {
64648
+ supportsProject: true,
64649
+ supportsSimulated: false,
64650
+ supportsGlobal: true,
64651
+ filePattern: "*.md"
64652
+ }
64653
+ }],
61386
64654
  ["cursor", {
61387
64655
  class: CursorSubagent,
61388
64656
  meta: {
@@ -61554,6 +64822,15 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
61554
64822
  filePattern: "*.md"
61555
64823
  }
61556
64824
  }],
64825
+ ["tabnine", {
64826
+ class: TabnineSubagent,
64827
+ meta: {
64828
+ supportsProject: true,
64829
+ supportsSimulated: false,
64830
+ supportsGlobal: true,
64831
+ filePattern: "*.md"
64832
+ }
64833
+ }],
61557
64834
  ["takt", {
61558
64835
  class: TaktSubagent,
61559
64836
  meta: {
@@ -63105,6 +66382,95 @@ var AugmentcodeRule = class AugmentcodeRule extends ToolRule {
63105
66382
  }
63106
66383
  };
63107
66384
  //#endregion
66385
+ //#region src/features/rules/bob-rule.ts
66386
+ /**
66387
+ * Rule generator for IBM Bob (Bob IDE and Bob Shell).
66388
+ *
66389
+ * Bob auto-loads plain-markdown instruction files with no frontmatter:
66390
+ *
66391
+ * - Project scope: the cross-tool `AGENTS.md` at the project root plus every
66392
+ * `.bob/rules/*.md` file (read recursively in alphabetical order).
66393
+ * - Global scope: `~/.bob/AGENTS.md` plus `~/.bob/rules/*.md`.
66394
+ *
66395
+ * The workspace files override the user files. Bob's mode-specific
66396
+ * `.bob/rules-{mode}/` directories are left alone: rulesync has no notion of
66397
+ * Bob's modes, and a rule that should apply everywhere belongs in `.bob/rules/`.
66398
+ *
66399
+ * @see https://bob.ibm.com/docs/ide/configuration/rules
66400
+ * @see https://bob.ibm.com/docs/shell/configuration/configuring (user-scoped `~/.bob/AGENTS.md`)
66401
+ */
66402
+ var BobRule = class BobRule extends ToolRule {
66403
+ static getSettablePaths({ global, excludeToolDir } = {}) {
66404
+ return {
66405
+ root: {
66406
+ relativeDirPath: global ? buildToolPath(BOB_DIR, ".", excludeToolDir) : ".",
66407
+ relativeFilePath: BOB_RULE_FILE_NAME
66408
+ },
66409
+ nonRoot: { relativeDirPath: buildToolPath(BOB_DIR, "rules", excludeToolDir) }
66410
+ };
66411
+ }
66412
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
66413
+ const paths = this.getSettablePaths({ global });
66414
+ if (relativeFilePath === paths.root.relativeFilePath) {
66415
+ const fileContent = await readFileContent(join(outputRoot, paths.root.relativeDirPath, paths.root.relativeFilePath));
66416
+ return new BobRule({
66417
+ outputRoot,
66418
+ relativeDirPath: paths.root.relativeDirPath,
66419
+ relativeFilePath: paths.root.relativeFilePath,
66420
+ fileContent,
66421
+ validate,
66422
+ root: true
66423
+ });
66424
+ }
66425
+ const fileContent = await readFileContent(join(outputRoot, paths.nonRoot.relativeDirPath, relativeFilePath));
66426
+ return new BobRule({
66427
+ outputRoot,
66428
+ relativeDirPath: paths.nonRoot.relativeDirPath,
66429
+ relativeFilePath,
66430
+ fileContent,
66431
+ validate,
66432
+ root: false
66433
+ });
66434
+ }
66435
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
66436
+ const paths = this.getSettablePaths({ global });
66437
+ return new BobRule(this.buildToolRuleParamsDefault({
66438
+ outputRoot,
66439
+ rulesyncRule,
66440
+ validate,
66441
+ rootPath: paths.root,
66442
+ nonRootPath: paths.nonRoot
66443
+ }));
66444
+ }
66445
+ toRulesyncRule() {
66446
+ return this.toRulesyncRuleDefault();
66447
+ }
66448
+ validate() {
66449
+ return {
66450
+ success: true,
66451
+ error: null
66452
+ };
66453
+ }
66454
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
66455
+ const paths = this.getSettablePaths({ global });
66456
+ const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath !== paths.nonRoot.relativeDirPath;
66457
+ return new BobRule({
66458
+ outputRoot,
66459
+ relativeDirPath,
66460
+ relativeFilePath,
66461
+ fileContent: "",
66462
+ validate: false,
66463
+ root: isRoot
66464
+ });
66465
+ }
66466
+ static isTargetedByRulesyncRule(rulesyncRule) {
66467
+ return this.isTargetedByRulesyncRuleDefault({
66468
+ rulesyncRule,
66469
+ toolTarget: "bob"
66470
+ });
66471
+ }
66472
+ };
66473
+ //#endregion
63108
66474
  //#region src/features/rules/claudecode-language-settings.ts
63109
66475
  /**
63110
66476
  * Claude Code's native home for the root `language` key of `rulesync.jsonc`.
@@ -64319,6 +67685,73 @@ var CopilotcliRule = class CopilotcliRule extends CopilotRule {
64319
67685
  }
64320
67686
  };
64321
67687
  //#endregion
67688
+ //#region src/features/rules/cortexcode-rule.ts
67689
+ var CortexcodeRule = class CortexcodeRule extends ToolRule {
67690
+ constructor({ fileContent, root, ...rest }) {
67691
+ super({
67692
+ ...rest,
67693
+ fileContent,
67694
+ root: root ?? false
67695
+ });
67696
+ }
67697
+ static getSettablePaths(_options = {}) {
67698
+ return { root: {
67699
+ relativeDirPath: ".",
67700
+ relativeFilePath: CORTEXCODE_RULE_FILE_NAME
67701
+ } };
67702
+ }
67703
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
67704
+ const { root } = this.getSettablePaths({ global });
67705
+ const relativePath = join(root.relativeDirPath, root.relativeFilePath);
67706
+ const fileContent = await readFileContent(join(outputRoot, relativePath));
67707
+ return new CortexcodeRule({
67708
+ outputRoot,
67709
+ relativeDirPath: root.relativeDirPath,
67710
+ relativeFilePath: root.relativeFilePath,
67711
+ fileContent,
67712
+ validate,
67713
+ root: true
67714
+ });
67715
+ }
67716
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
67717
+ const { root } = this.getSettablePaths({ global });
67718
+ const isRoot = rulesyncRule.getFrontmatter().root ?? false;
67719
+ return new CortexcodeRule({
67720
+ outputRoot,
67721
+ relativeDirPath: root.relativeDirPath,
67722
+ relativeFilePath: root.relativeFilePath,
67723
+ fileContent: rulesyncRule.getBody(),
67724
+ validate,
67725
+ root: isRoot
67726
+ });
67727
+ }
67728
+ toRulesyncRule() {
67729
+ return this.toRulesyncRuleDefault();
67730
+ }
67731
+ validate() {
67732
+ return {
67733
+ success: true,
67734
+ error: null
67735
+ };
67736
+ }
67737
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
67738
+ return new CortexcodeRule({
67739
+ outputRoot,
67740
+ relativeDirPath,
67741
+ relativeFilePath,
67742
+ fileContent: "",
67743
+ validate: false,
67744
+ root: relativeFilePath === "AGENTS.md" && relativeDirPath === "."
67745
+ });
67746
+ }
67747
+ static isTargetedByRulesyncRule(rulesyncRule) {
67748
+ return this.isTargetedByRulesyncRuleDefault({
67749
+ rulesyncRule,
67750
+ toolTarget: "cortexcode"
67751
+ });
67752
+ }
67753
+ };
67754
+ //#endregion
64322
67755
  //#region src/features/rules/crush-rule.ts
64323
67756
  var CrushRule = class CrushRule extends ToolRule {
64324
67757
  constructor({ fileContent, root, ...rest }) {
@@ -67396,6 +70829,97 @@ var RovodevRule = class RovodevRule extends ToolRule {
67396
70829
  }
67397
70830
  };
67398
70831
  //#endregion
70832
+ //#region src/features/rules/tabnine-rule.ts
70833
+ /**
70834
+ * Rule generator for Tabnine CLI.
70835
+ *
70836
+ * - Project scope: `TABNINE.md` at the project root is the CLI's memory file
70837
+ * (loaded on every session, refreshed with `/memory reload`). Non-root
70838
+ * rules go to `.tabnine/guidelines/*.md`, the guideline directory the
70839
+ * Tabnine IDE agent reads; the CLI does not auto-load that directory, so
70840
+ * the processor lists the files from `TABNINE.md` (toon mode).
70841
+ * - Global scope: `~/.tabnine/agent/TABNINE.md` next to the other user-scoped
70842
+ * agent assets (the CLI documents no user-level memory file, so this path
70843
+ * mirrors the `~/.gemini/GEMINI.md` convention of the Gemini CLI it is
70844
+ * derived from) plus `~/.tabnine/guidelines/*.md`.
70845
+ *
70846
+ * Rule files are plain Markdown without frontmatter.
70847
+ *
70848
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference (`context.fileName`, `/memory`)
70849
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-agent/guidelines (`.tabnine/guidelines/`)
70850
+ */
70851
+ var TabnineRule = class TabnineRule extends ToolRule {
70852
+ static getSettablePaths({ global, excludeToolDir } = {}) {
70853
+ return {
70854
+ root: {
70855
+ relativeDirPath: global ? buildToolPath(TABNINE_AGENT_DIR_PATH, ".", excludeToolDir) : ".",
70856
+ relativeFilePath: TABNINE_RULE_FILE_NAME
70857
+ },
70858
+ nonRoot: { relativeDirPath: buildToolPath(TABNINE_DIR, "guidelines", excludeToolDir) }
70859
+ };
70860
+ }
70861
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
70862
+ const paths = this.getSettablePaths({ global });
70863
+ if (relativeFilePath === paths.root.relativeFilePath) {
70864
+ const fileContent = await readFileContent(join(outputRoot, paths.root.relativeDirPath, paths.root.relativeFilePath));
70865
+ return new TabnineRule({
70866
+ outputRoot,
70867
+ relativeDirPath: paths.root.relativeDirPath,
70868
+ relativeFilePath: paths.root.relativeFilePath,
70869
+ fileContent,
70870
+ validate,
70871
+ root: true
70872
+ });
70873
+ }
70874
+ const fileContent = await readFileContent(join(outputRoot, paths.nonRoot.relativeDirPath, relativeFilePath));
70875
+ return new TabnineRule({
70876
+ outputRoot,
70877
+ relativeDirPath: paths.nonRoot.relativeDirPath,
70878
+ relativeFilePath,
70879
+ fileContent,
70880
+ validate,
70881
+ root: false
70882
+ });
70883
+ }
70884
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
70885
+ const paths = this.getSettablePaths({ global });
70886
+ return new TabnineRule(this.buildToolRuleParamsDefault({
70887
+ outputRoot,
70888
+ rulesyncRule,
70889
+ validate,
70890
+ rootPath: paths.root,
70891
+ nonRootPath: paths.nonRoot
70892
+ }));
70893
+ }
70894
+ toRulesyncRule() {
70895
+ return this.toRulesyncRuleDefault();
70896
+ }
70897
+ validate() {
70898
+ return {
70899
+ success: true,
70900
+ error: null
70901
+ };
70902
+ }
70903
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
70904
+ const paths = this.getSettablePaths({ global });
70905
+ const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath !== paths.nonRoot.relativeDirPath;
70906
+ return new TabnineRule({
70907
+ outputRoot,
70908
+ relativeDirPath,
70909
+ relativeFilePath,
70910
+ fileContent: "",
70911
+ validate: false,
70912
+ root: isRoot
70913
+ });
70914
+ }
70915
+ static isTargetedByRulesyncRule(rulesyncRule) {
70916
+ return this.isTargetedByRulesyncRuleDefault({
70917
+ rulesyncRule,
70918
+ toolTarget: "tabnine"
70919
+ });
70920
+ }
70921
+ };
70922
+ //#endregion
67399
70923
  //#region src/features/rules/takt-rule.ts
67400
70924
  /**
67401
70925
  * Default facet directory for TAKT rule files.
@@ -67958,6 +71482,14 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
67958
71482
  ruleDiscoveryMode: "toon"
67959
71483
  }
67960
71484
  }],
71485
+ ["bob", {
71486
+ class: BobRule,
71487
+ meta: {
71488
+ extension: "md",
71489
+ supportsGlobal: true,
71490
+ ruleDiscoveryMode: "auto"
71491
+ }
71492
+ }],
67961
71493
  ["claudecode", {
67962
71494
  class: ClaudecodeRule,
67963
71495
  meta: {
@@ -68021,6 +71553,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
68021
71553
  ruleDiscoveryMode: "auto"
68022
71554
  }
68023
71555
  }],
71556
+ ["cortexcode", {
71557
+ class: CortexcodeRule,
71558
+ meta: {
71559
+ extension: "md",
71560
+ supportsGlobal: false,
71561
+ ruleDiscoveryMode: "auto",
71562
+ collisionPolicy: "fold"
71563
+ }
71564
+ }],
68024
71565
  ["crush", {
68025
71566
  class: CrushRule,
68026
71567
  meta: {
@@ -68227,6 +71768,14 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
68227
71768
  localRootFileName: "AGENTS.local.md"
68228
71769
  }
68229
71770
  }],
71771
+ ["tabnine", {
71772
+ class: TabnineRule,
71773
+ meta: {
71774
+ extension: "md",
71775
+ supportsGlobal: true,
71776
+ ruleDiscoveryMode: "toon"
71777
+ }
71778
+ }],
68230
71779
  ["takt", {
68231
71780
  class: TaktRule,
68232
71781
  meta: {
@@ -71474,6 +75023,6 @@ async function importChecksCore(params) {
71474
75023
  return writtenCount;
71475
75024
  }
71476
75025
  //#endregion
71477
- export { RulesyncCheck as $, ALL_TOOL_TARGETS as $t, CLAUDECODE_SKILLS_DIR_PATH as A, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as An, ensureDir as At, RulesyncSkill as B, quoteForLog as Bn, pathEscapesRoot as Bt, ChecksProcessor as C, RULESYNC_PERMISSIONS_FILE_NAME as Cn, applyFileMode as Ct, CLAUDECODE_LOCAL_RULE_FILE_NAME as D, RULESYNC_RELATIVE_DIR_PATH as Dn, checkPathTraversal as Dt, CLAUDECODE_DIR as E, RULESYNC_PERMISSIONS_SCHEMA_URL as En, assertWritablePathInsideRoot as Et, AUGMENTCODE_DIR as F, DEPRECATED_FEATURE_REPLACEMENTS as Fn, isFileSystemError as Ft, RulesyncMcp as G, removeFile as Gt, RulesyncRule as H, stripControlCharactersKeepingLineFeeds as Hn, readFileContentOrNull as Ht, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as I, formatError as In, isSymlink as It, getRulesyncSourceCandidates as J, resolvePath as Jt, RulesyncIgnore as K, removeFileStrict as Kt, getLocalSkillDirNames as L, truncateText as Ln, listDirectoryEntryNames as Lt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as M, parseCommaSeparatedList as Mn, getFileSize as Mt, caseFoldIdentity as N, ALL_FEATURES as Nn, getHomeDirectory as Nt, CLAUDECODE_MEMORIES_DIR_NAME as O, RULESYNC_RULES_RELATIVE_DIR_PATH as On, createTempDirectory as Ot, groupSpellingsByCaseFoldedIdentity as P, ALL_FEATURES_WITH_WILDCARD as Pn, isFileNotFoundError as Pt, RulesyncCommandFrontmatterSchema as Q, writeFileContent as Qt, RulesyncSubagent as R, hasDeceptiveHiddenCharacters as Rn, listFilePathsRecursively as Rt, QWENCODE_LOCAL_RULE_FILE_NAME as S, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Sn, ErrorCodes as St, CODEXCLI_DIR as T, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Tn, assertTreeContainsNoSymlinks as Tt, RulesyncRuleFrontmatterSchema as U, stripHiddenCharacters as Un, removeDirectory as Ut, RulesyncSkillFrontmatterSchema as V, stripControlCharacters as Vn, readFileContent as Vt, RulesyncPermissions as W, removeDirectoryStrict as Wt, parseJsonc as X, toPosixPath as Xt, resolveRulesyncSourceWritePath as Y, runWithDirectoryRollback as Yt, RulesyncCommand as Z, writeFileBuffer as Zt, IgnoreProcessor as _, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as _n, warnOnConflictingFlags as _t, getProcessorRegistryEntry as a, RULESYNC_AIIGNORE_FILE_NAME as an, ConfigResolver as at, CommandsProcessor as b, RULESYNC_MCP_RELATIVE_FILE_PATH as bn, withWarnOnceScope as bt, RulesProcessor as c, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as cn, CONFLICTING_TARGET_PAIRS as ct, CODEBUDDY_DIR as d, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as dn, SourceEntrySchema as dt, ALL_TOOL_TARGETS_WITH_WILDCARD as en, RulesyncCheckFrontmatterSchema as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as fn, findControlCharacter as ft, McpProcessor as g, RULESYNC_IGNORE_RELATIVE_FILE_PATH as gn, fallbackLogger as gt, shortenToWidth as h, RULESYNC_HOOKS_RELATIVE_FILE_PATH as hn, WarningCollectingLogger as ht, inspectInputRoots as i, MAX_FILE_SIZE as in, SKILL_FILE_NAME as it, FACTORYDROID_DIR as j, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as jn, fileExists as jt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as k, RULESYNC_SKILLS_RELATIVE_DIR_PATH as kn, directoryExists as kt, SubagentsProcessor as l, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ln, ConfigFileSchema as lt, displayWidthOf as m, RULESYNC_HOOKS_LEGACY_FILE_NAME as mn, JsonLogger as mt, formatSourceLoadFailure as n, ToolTargetSchema as nn, loadYaml as nt, convertFromTool as o, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as on, mergeInputRootConfigs as ot, ELLIPSIS_WIDTH as p, RULESYNC_HOOKS_FILE_NAME as pn, ConsoleLogger as pt, RulesyncHooks as q, removeTempDirectory as qt, generate as r, CURATED_RULES_FEATURE_SUBDIR as rn, SHARED_USER_MANAGED_CONFIG_PATHS as rt, isPackagingToolTarget as s, RULESYNC_CHECKS_RELATIVE_DIR_PATH as sn, resolveEffectiveInputRoots as st, importFromTool as t, PACKAGING_TOOL_TARGETS as tn, stringifyFrontmatter as tt, SkillsProcessor as u, RULESYNC_CONFIG_SCHEMA_URL as un, GITIGNORE_DESTINATION_KEY as ut, CRUSH_LOCAL_RULE_FILE_NAME as v, RULESYNC_MCP_FILE_NAME as vn, withFallbackLoggerTarget as vt, CODEXCLI_BASH_RULES_FILE_NAME as w, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as wn, assertDirectoryIfExists as wt, QWENCODE_DIR as x, RULESYNC_MCP_SCHEMA_URL as xn, CLIError as xt, HooksProcessor as y, RULESYNC_MCP_LEGACY_FILE_NAME as yn, resetRunWarningState as yt, RulesyncSubagentFrontmatterSchema as z, hasEnclosingMarkOutsideKeycap as zn, listSubdirectoryNames as zt };
75026
+ 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 };
71478
75027
 
71479
- //# sourceMappingURL=import-BknrMODR.js.map
75028
+ //# sourceMappingURL=import-D2GhBXJV.js.map