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.
@@ -444,34 +444,36 @@ const isFeatureValueEnabled = (value) => {
444
444
  const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
445
445
  //#endregion
446
446
  //#region src/constants/rulesync-paths.ts
447
- const { join: join$310 } = node_path.posix;
447
+ const { join: join$332 } = node_path.posix;
448
448
  const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
449
449
  const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
450
+ const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
451
+ const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
450
452
  const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
451
453
  const RULES_FEATURE_SUBDIR = "rules";
452
- const CURATED_RULES_FEATURE_SUBDIR = join$310(RULES_FEATURE_SUBDIR, ".curated");
454
+ const CURATED_RULES_FEATURE_SUBDIR = join$332(RULES_FEATURE_SUBDIR, ".curated");
453
455
  const COMMANDS_FEATURE_SUBDIR = "commands";
454
456
  const SUBAGENTS_FEATURE_SUBDIR = "subagents";
455
457
  const CHECKS_FEATURE_SUBDIR = "checks";
456
458
  const SKILLS_FEATURE_SUBDIR = "skills";
457
- const CURATED_SKILLS_FEATURE_SUBDIR = join$310(SKILLS_FEATURE_SUBDIR, ".curated");
458
- const RULESYNC_RULES_RELATIVE_DIR_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
459
- const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
460
- const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
461
- const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
462
- const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
463
- const RULESYNC_MCP_RELATIVE_FILE_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
464
- const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
465
- const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
466
- join$310(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
467
- const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
468
- const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
459
+ const CURATED_SKILLS_FEATURE_SUBDIR = join$332(SKILLS_FEATURE_SUBDIR, ".curated");
460
+ const RULESYNC_RULES_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
461
+ const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
462
+ const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
463
+ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
464
+ const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
465
+ const RULESYNC_MCP_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
466
+ const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
467
+ const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
468
+ join$332(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
469
+ const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
470
+ const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
469
471
  const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
470
- const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
472
+ const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
471
473
  const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
472
474
  const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
473
- const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
474
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$310(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
475
+ const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
476
+ const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$332(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
475
477
  const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
476
478
  const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
477
479
  const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
@@ -495,6 +497,7 @@ const rulesProcessorToolTargetTuple = [
495
497
  "antigravity-plugin",
496
498
  "augmentcode",
497
499
  "augmentcode-legacy",
500
+ "bob",
498
501
  "claudecode",
499
502
  "claudecode-legacy",
500
503
  "cline",
@@ -502,6 +505,7 @@ const rulesProcessorToolTargetTuple = [
502
505
  "codexcli",
503
506
  "copilot",
504
507
  "copilotcli",
508
+ "cortexcode",
505
509
  "crush",
506
510
  "cursor",
507
511
  "deepagents",
@@ -523,6 +527,7 @@ const rulesProcessorToolTargetTuple = [
523
527
  "replit",
524
528
  "roo",
525
529
  "rovodev",
530
+ "tabnine",
526
531
  "takt",
527
532
  "vibe",
528
533
  "warp",
@@ -537,6 +542,7 @@ const ignoreProcessorToolTargetTuple = [
537
542
  "aiassistant",
538
543
  "antigravity-cli",
539
544
  "augmentcode",
545
+ "bob",
540
546
  "claudecode",
541
547
  "claudecode-legacy",
542
548
  "cline",
@@ -551,6 +557,7 @@ const ignoreProcessorToolTargetTuple = [
551
557
  "qwencode",
552
558
  "reasonix",
553
559
  "roo",
560
+ "tabnine",
554
561
  "devin",
555
562
  "vibe",
556
563
  "warp",
@@ -564,6 +571,7 @@ const mcpProcessorToolTargetTuple = [
564
571
  "antigravity-ide",
565
572
  "antigravity-plugin",
566
573
  "augmentcode",
574
+ "bob",
567
575
  "claudecode",
568
576
  "claudecode-plugin",
569
577
  "claudecode-legacy",
@@ -571,6 +579,7 @@ const mcpProcessorToolTargetTuple = [
571
579
  "codexcli",
572
580
  "copilot",
573
581
  "copilotcli",
582
+ "cortexcode",
574
583
  "cursor",
575
584
  "deepagents",
576
585
  "factorydroid",
@@ -589,6 +598,7 @@ const mcpProcessorToolTargetTuple = [
589
598
  "reasonix",
590
599
  "roo",
591
600
  "rovodev",
601
+ "tabnine",
592
602
  "takt",
593
603
  "vibe",
594
604
  "warp",
@@ -602,6 +612,7 @@ const commandsProcessorToolTargetTuple = [
602
612
  "antigravity-cli",
603
613
  "antigravity-ide",
604
614
  "augmentcode",
615
+ "bob",
605
616
  "claudecode",
606
617
  "claudecode-plugin",
607
618
  "claudecode-legacy",
@@ -625,6 +636,7 @@ const commandsProcessorToolTargetTuple = [
625
636
  "roo",
626
637
  "zoocode",
627
638
  "rovodev",
639
+ "tabnine",
628
640
  "takt",
629
641
  "devin",
630
642
  "warp",
@@ -644,6 +656,7 @@ const subagentsProcessorToolTargetTuple = [
644
656
  "codexcli",
645
657
  "copilot",
646
658
  "copilotcli",
659
+ "cortexcode",
647
660
  "cursor",
648
661
  "deepagents",
649
662
  "devin",
@@ -662,6 +675,7 @@ const subagentsProcessorToolTargetTuple = [
662
675
  "roo",
663
676
  "zoocode",
664
677
  "rovodev",
678
+ "tabnine",
665
679
  "takt",
666
680
  "vibe",
667
681
  "zcode"
@@ -675,6 +689,7 @@ const skillsProcessorToolTargetTuple = [
675
689
  "antigravity-ide",
676
690
  "antigravity-plugin",
677
691
  "augmentcode",
692
+ "bob",
678
693
  "claudecode",
679
694
  "claudecode-plugin",
680
695
  "claudecode-legacy",
@@ -682,6 +697,7 @@ const skillsProcessorToolTargetTuple = [
682
697
  "codexcli",
683
698
  "copilot",
684
699
  "copilotcli",
700
+ "cortexcode",
685
701
  "crush",
686
702
  "cursor",
687
703
  "deepagents",
@@ -703,6 +719,7 @@ const skillsProcessorToolTargetTuple = [
703
719
  "replit",
704
720
  "roo",
705
721
  "rovodev",
722
+ "tabnine",
706
723
  "takt",
707
724
  "vibe",
708
725
  "warp",
@@ -725,6 +742,7 @@ const hooksProcessorToolTargetTuple = [
725
742
  "codexcli",
726
743
  "copilot",
727
744
  "copilotcli",
745
+ "cortexcode",
728
746
  "opencode",
729
747
  "pi",
730
748
  "factorydroid",
@@ -737,10 +755,12 @@ const hooksProcessorToolTargetTuple = [
737
755
  "kiro-ide",
738
756
  "devin",
739
757
  "augmentcode",
758
+ "bob",
740
759
  "junie",
741
760
  "vibe",
742
761
  "qwencode",
743
762
  "reasonix",
763
+ "tabnine",
744
764
  "grokcli",
745
765
  "zcode"
746
766
  ];
@@ -773,6 +793,7 @@ const permissionsProcessorToolTargetTuple = [
773
793
  "reasonix",
774
794
  "roo",
775
795
  "rovodev",
796
+ "tabnine",
776
797
  "takt",
777
798
  "vibe",
778
799
  "warp",
@@ -3258,16 +3279,21 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
3258
3279
  ".amp/settings.json",
3259
3280
  ".amp/settings.jsonc",
3260
3281
  ".antigravity/settings.json",
3282
+ ".bob/settings.json",
3283
+ ".bob/settings/settings.json",
3261
3284
  ".claude/settings.json",
3262
3285
  ".claude/settings.local.json",
3263
3286
  ".codex/config.toml",
3264
3287
  ".copilot/settings.json",
3265
3288
  ".github/copilot/settings.json",
3289
+ ".cortex/settings.json",
3290
+ ".snowflake/cortex/hooks.json",
3266
3291
  ".devin/config.json",
3267
3292
  ".factory/settings.json",
3268
3293
  ".grok/config.toml",
3269
3294
  ".rovodev/config.yml",
3270
3295
  ".rovodev/mcp.json",
3296
+ ".tabnine/agent/settings.json",
3271
3297
  ".vibe/config.toml",
3272
3298
  ".zcode/config.json",
3273
3299
  ".zcode/cli/config.json",
@@ -4778,6 +4804,110 @@ const CANONICAL_TO_ZCODE_EVENT_NAMES = {
4778
4804
  };
4779
4805
  const ZCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4780
4806
  /**
4807
+ * Hook events supported by IBM Bob's lifecycle hooks.
4808
+ *
4809
+ * Bob reads `hooks` from `.bob/settings.json` (project) and
4810
+ * `~/.bob/settings/settings.json` (user) in the Claude-Code shape and fires
4811
+ * five events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse and
4812
+ * Stop. Only `command` hooks exist, and `matcher` (a regex over the tool
4813
+ * name) applies to the two tool events.
4814
+ *
4815
+ * @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
4816
+ */
4817
+ const BOB_HOOK_EVENTS = [
4818
+ "sessionStart",
4819
+ "beforeSubmitPrompt",
4820
+ "preToolUse",
4821
+ "postToolUse",
4822
+ "stop"
4823
+ ];
4824
+ const CANONICAL_TO_BOB_EVENT_NAMES = {
4825
+ sessionStart: "SessionStart",
4826
+ beforeSubmitPrompt: "UserPromptSubmit",
4827
+ preToolUse: "PreToolUse",
4828
+ postToolUse: "PostToolUse",
4829
+ stop: "Stop"
4830
+ };
4831
+ const BOB_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_BOB_EVENT_NAMES).map(([k, v]) => [v, k]));
4832
+ /**
4833
+ * Hook events supported by Tabnine CLI.
4834
+ *
4835
+ * Tabnine CLI reads `hooks` from `.tabnine/agent/settings.json` (project) and
4836
+ * `~/.tabnine/agent/settings.json` (user) and fires eleven events with
4837
+ * PascalCase names. Only `command` hooks exist; `matcher` is a regex over the
4838
+ * tool name for the two tool events and an exact string for the lifecycle
4839
+ * events, and `timeout` is expressed in milliseconds.
4840
+ *
4841
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks
4842
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks/configuration
4843
+ */
4844
+ const TABNINE_HOOK_EVENTS = [
4845
+ "sessionStart",
4846
+ "sessionEnd",
4847
+ "beforeSubmitPrompt",
4848
+ "stop",
4849
+ "preModelInvocation",
4850
+ "postModelInvocation",
4851
+ "beforeToolSelection",
4852
+ "preToolUse",
4853
+ "postToolUse",
4854
+ "preCompact",
4855
+ "notification"
4856
+ ];
4857
+ const CANONICAL_TO_TABNINE_EVENT_NAMES = {
4858
+ sessionStart: "SessionStart",
4859
+ sessionEnd: "SessionEnd",
4860
+ beforeSubmitPrompt: "BeforeAgent",
4861
+ stop: "AfterAgent",
4862
+ preModelInvocation: "BeforeModel",
4863
+ postModelInvocation: "AfterModel",
4864
+ beforeToolSelection: "BeforeToolSelection",
4865
+ preToolUse: "BeforeTool",
4866
+ postToolUse: "AfterTool",
4867
+ preCompact: "PreCompress",
4868
+ notification: "Notification"
4869
+ };
4870
+ const TABNINE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_TABNINE_EVENT_NAMES).map(([k, v]) => [v, k]));
4871
+ /**
4872
+ * Hook events supported by Snowflake Cortex Code.
4873
+ *
4874
+ * Cortex Code reads `hooks` from `.cortex/settings.json` (project) and
4875
+ * `~/.snowflake/cortex/hooks.json` (user) in the Claude-Code-shaped layout:
4876
+ * PascalCase event names, a regex `matcher` over the tool name, and `command`
4877
+ * or `prompt` hooks with a `timeout` in seconds and an optional `enabled`
4878
+ * flag. `$CORTEX_PROJECT_DIR` resolves to the project root inside commands.
4879
+ *
4880
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility
4881
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/settings
4882
+ */
4883
+ const CORTEXCODE_HOOK_EVENTS = [
4884
+ "preToolUse",
4885
+ "postToolUse",
4886
+ "permissionRequest",
4887
+ "beforeSubmitPrompt",
4888
+ "sessionStart",
4889
+ "sessionEnd",
4890
+ "preCompact",
4891
+ "stop",
4892
+ "subagentStop",
4893
+ "notification",
4894
+ "setup"
4895
+ ];
4896
+ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
4897
+ preToolUse: "PreToolUse",
4898
+ postToolUse: "PostToolUse",
4899
+ permissionRequest: "PermissionRequest",
4900
+ beforeSubmitPrompt: "UserPromptSubmit",
4901
+ sessionStart: "SessionStart",
4902
+ sessionEnd: "SessionEnd",
4903
+ preCompact: "PreCompact",
4904
+ stop: "Stop",
4905
+ subagentStop: "SubagentStop",
4906
+ notification: "Notification",
4907
+ setup: "Setup"
4908
+ };
4909
+ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4910
+ /**
4781
4911
  * Hook events supported by Hermes Agent's native Shell Hooks system.
4782
4912
  *
4783
4913
  * Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
@@ -4930,6 +5060,9 @@ const HooksConfigSchema = zod_mini.z.looseObject({
4930
5060
  grokcli: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
4931
5061
  "kimi-code": zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
4932
5062
  zcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
5063
+ bob: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
5064
+ cortexcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
5065
+ tabnine: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
4933
5066
  qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
4934
5067
  hooks: zod_mini.z.optional(hooksRecordSchema),
4935
5068
  disableAllHooks: zod_mini.z.optional(zod_mini.z.boolean())
@@ -5931,6 +6064,7 @@ const RulesyncMcpFileSchema = zod_mini.z.looseObject({
5931
6064
  "antigravity-ide": zod_mini.z.optional(toolScopedMcpSchema),
5932
6065
  aiassistant: zod_mini.z.optional(toolScopedMcpSchema),
5933
6066
  augmentcode: zod_mini.z.optional(toolScopedMcpSchema),
6067
+ bob: zod_mini.z.optional(toolScopedMcpSchema),
5934
6068
  claudecode: zod_mini.z.optional(toolScopedMcpSchema),
5935
6069
  cline: zod_mini.z.optional(toolScopedMcpSchema),
5936
6070
  codexcli: zod_mini.z.optional(toolScopedMcpSchema),
@@ -5952,6 +6086,8 @@ const RulesyncMcpFileSchema = zod_mini.z.looseObject({
5952
6086
  reasonix: zod_mini.z.optional(toolScopedMcpSchema),
5953
6087
  roo: zod_mini.z.optional(toolScopedMcpSchema),
5954
6088
  rovodev: zod_mini.z.optional(toolScopedMcpSchema),
6089
+ cortexcode: zod_mini.z.optional(toolScopedMcpSchema),
6090
+ tabnine: zod_mini.z.optional(toolScopedMcpSchema),
5955
6091
  takt: zod_mini.z.optional(toolScopedMcpSchema),
5956
6092
  vibe: zod_mini.z.optional(toolScopedMcpSchema),
5957
6093
  warp: zod_mini.z.optional(toolScopedMcpSchema),
@@ -6773,6 +6909,27 @@ const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
6773
6909
  autoMode: zod_mini.z.optional(zod_mini.z.looseObject({}))
6774
6910
  });
6775
6911
  /**
6912
+ * Tool-scoped override block for Tabnine CLI. The canonical `permission` block
6913
+ * drives only the `tools.allowed` / `tools.exclude` lists of
6914
+ * `.tabnine/agent/settings.json`; the other `tools.*` keys (`core`,
6915
+ * `enableCoaching`, `useRipgrep`, `shell.*`, `enableWebTools`, ...) and the
6916
+ * `general.defaultApprovalMode` switch have no canonical category, so they are
6917
+ * authored here, deep-merged into the settings file, and emitted only for
6918
+ * Tabnine. Import lifts the entries rulesync cannot map back onto a canonical
6919
+ * category into this block so a regenerate does not drop them. Kept
6920
+ * `looseObject` (verbatim passthrough). Both project and global scope are
6921
+ * supported.
6922
+ *
6923
+ * @example
6924
+ * { "tools": { "core": ["read_file", "run_shell_command(git)"] },
6925
+ * "general": { "defaultApprovalMode": "plan" } }
6926
+ */
6927
+ const TabninePermissionsOverrideSchema = zod_mini.z.looseObject({
6928
+ permission: zod_mini.z.optional(ToolScopedPermissionSchema),
6929
+ tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
6930
+ general: zod_mini.z.optional(zod_mini.z.looseObject({}))
6931
+ });
6932
+ /**
6776
6933
  * Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
6777
6934
  * to per-tool allow/ask/deny with no canonical category — the `[sandbox]`
6778
6935
  * enforcement table (`workspace_root`, `allow_write`, `forbid_read`, `bash`,
@@ -7533,6 +7690,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
7533
7690
  factorydroid: zod_mini.z.optional(FactorydroidPermissionsOverrideSchema),
7534
7691
  warp: zod_mini.z.optional(WarpPermissionsOverrideSchema),
7535
7692
  junie: zod_mini.z.optional(JuniePermissionsOverrideSchema),
7693
+ tabnine: zod_mini.z.optional(TabninePermissionsOverrideSchema),
7536
7694
  takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
7537
7695
  amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
7538
7696
  "antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema),
@@ -9180,6 +9338,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
9180
9338
  })),
9181
9339
  cline: zod_mini.z.optional(zod_mini.z.looseObject({})),
9182
9340
  roo: zod_mini.z.optional(zod_mini.z.looseObject({})),
9341
+ bob: zod_mini.z.optional(zod_mini.z.looseObject({})),
9183
9342
  amp: zod_mini.z.optional(zod_mini.z.looseObject({})),
9184
9343
  devin: zod_mini.z.optional(zod_mini.z.looseObject({
9185
9344
  "argument-hint": zod_mini.z.optional(zod_mini.z.string()),
@@ -9257,6 +9416,8 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
9257
9416
  "user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
9258
9417
  "allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
9259
9418
  })),
9419
+ cortexcode: zod_mini.z.optional(zod_mini.z.looseObject({})),
9420
+ tabnine: zod_mini.z.optional(zod_mini.z.looseObject({})),
9260
9421
  takt: zod_mini.z.optional(zod_mini.z.looseObject({
9261
9422
  name: zod_mini.z.optional(zod_mini.z.string()),
9262
9423
  extends: zod_mini.z.optional(zod_mini.z.string())
@@ -12819,6 +12980,53 @@ const SHARED_CONFIG_OWNERSHIP = {
12819
12980
  },
12820
12981
  ".zcode/config.json": ZCODE_WORKSPACE_CONFIG_DECLARATION,
12821
12982
  ".zcode/cli/config.json": ZCODE_USER_CONFIG_DECLARATION,
12983
+ ".bob/settings.json": {
12984
+ format: "json",
12985
+ invalidRootPolicy: "error",
12986
+ features: { hooks: {
12987
+ kind: "replace-owned-keys",
12988
+ ownedKeys: ["hooks"]
12989
+ } }
12990
+ },
12991
+ ".bob/settings/settings.json": {
12992
+ format: "json",
12993
+ invalidRootPolicy: "error",
12994
+ features: { hooks: {
12995
+ kind: "replace-owned-keys",
12996
+ ownedKeys: ["hooks"]
12997
+ } }
12998
+ },
12999
+ ".cortex/settings.json": {
13000
+ format: "json",
13001
+ invalidRootPolicy: "error",
13002
+ features: { hooks: {
13003
+ kind: "replace-owned-keys",
13004
+ ownedKeys: ["hooks"]
13005
+ } }
13006
+ },
13007
+ ".snowflake/cortex/hooks.json": {
13008
+ format: "json",
13009
+ invalidRootPolicy: "error",
13010
+ features: { hooks: {
13011
+ kind: "replace-owned-keys",
13012
+ ownedKeys: ["hooks"]
13013
+ } }
13014
+ },
13015
+ ".tabnine/agent/settings.json": {
13016
+ format: "json",
13017
+ invalidRootPolicy: "error",
13018
+ features: {
13019
+ mcp: {
13020
+ kind: "replace-owned-keys",
13021
+ ownedKeys: ["mcpServers"]
13022
+ },
13023
+ hooks: {
13024
+ kind: "replace-owned-keys",
13025
+ ownedKeys: ["hooks"]
13026
+ },
13027
+ permissions: { kind: "deep-merge" }
13028
+ }
13029
+ },
12822
13030
  ".kiro/agents/default.json": {
12823
13031
  format: "json",
12824
13032
  features: {
@@ -14511,6 +14719,140 @@ var AugmentcodeCommand = class AugmentcodeCommand extends ToolCommand {
14511
14719
  }
14512
14720
  };
14513
14721
  //#endregion
14722
+ //#region src/constants/bob-paths.ts
14723
+ const BOB_DIR = ".bob";
14724
+ const BOB_RULE_FILE_NAME = "AGENTS.md";
14725
+ (0, node_path.join)(BOB_DIR, "rules");
14726
+ const BOB_IGNORE_FILE_NAME = ".bobignore";
14727
+ const BOB_MCP_FILE_NAME = "mcp.json";
14728
+ const BOB_COMMANDS_DIR_PATH = (0, node_path.join)(BOB_DIR, "commands");
14729
+ const BOB_SKILLS_DIR_PATH = (0, node_path.join)(BOB_DIR, "skills");
14730
+ const BOB_SETTINGS_FILE_NAME = "settings.json";
14731
+ const BOB_GLOBAL_SETTINGS_DIR_PATH = (0, node_path.join)(BOB_DIR, "settings");
14732
+ //#endregion
14733
+ //#region src/features/commands/bob-command.ts
14734
+ /**
14735
+ * IBM Bob slash commands are Markdown files under `<project>/.bob/commands/`
14736
+ * (project scope) and `~/.bob/commands/` (user scope), invoked from the chat
14737
+ * input with `/`. The file name is the command name; the optional
14738
+ * `description` / `argument-hint` frontmatter keys are the same ones the
14739
+ * Claude-Code-lineage tools use, and the body is the prompt Bob runs.
14740
+ *
14741
+ * @see https://bob.ibm.com/docs/ide/features/slash-commands
14742
+ * @see https://bob.ibm.com/docs/shell/features/slash-commands
14743
+ */
14744
+ const BobCommandFrontmatterSchema = zod_mini.z.looseObject({
14745
+ description: zod_mini.z.optional(zod_mini.z.string()),
14746
+ "argument-hint": zod_mini.z.optional(zod_mini.z.string())
14747
+ });
14748
+ var BobCommand = class BobCommand extends ToolCommand {
14749
+ frontmatter;
14750
+ body;
14751
+ constructor({ frontmatter, body, ...rest }) {
14752
+ if (rest.validate) {
14753
+ const result = BobCommandFrontmatterSchema.safeParse(frontmatter);
14754
+ if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
14755
+ }
14756
+ super({
14757
+ ...rest,
14758
+ fileContent: stringifyFrontmatter(body, frontmatter)
14759
+ });
14760
+ this.frontmatter = frontmatter;
14761
+ this.body = body;
14762
+ }
14763
+ static getSettablePaths(_options = {}) {
14764
+ return { relativeDirPath: BOB_COMMANDS_DIR_PATH };
14765
+ }
14766
+ getBody() {
14767
+ return this.body;
14768
+ }
14769
+ getFrontmatter() {
14770
+ return this.frontmatter;
14771
+ }
14772
+ toRulesyncCommand() {
14773
+ const { description, ...restFields } = this.frontmatter;
14774
+ const rulesyncFrontmatter = {
14775
+ targets: ["*"],
14776
+ description,
14777
+ ...Object.keys(restFields).length > 0 && { bob: restFields }
14778
+ };
14779
+ const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
14780
+ return new RulesyncCommand({
14781
+ outputRoot: ".",
14782
+ frontmatter: rulesyncFrontmatter,
14783
+ body: this.body,
14784
+ relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
14785
+ relativeFilePath: this.relativeFilePath,
14786
+ fileContent,
14787
+ validate: true
14788
+ });
14789
+ }
14790
+ static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
14791
+ const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
14792
+ const bobFields = rulesyncFrontmatter.bob ?? {};
14793
+ const bobFrontmatter = {
14794
+ description: rulesyncFrontmatter.description,
14795
+ ...bobFields
14796
+ };
14797
+ const body = rulesyncCommand.getBody();
14798
+ const paths = this.getSettablePaths({ global });
14799
+ return new BobCommand({
14800
+ outputRoot,
14801
+ frontmatter: bobFrontmatter,
14802
+ body,
14803
+ relativeDirPath: paths.relativeDirPath,
14804
+ relativeFilePath: rulesyncCommand.getRelativeFilePath(),
14805
+ validate
14806
+ });
14807
+ }
14808
+ validate() {
14809
+ if (!this.frontmatter) return {
14810
+ success: true,
14811
+ error: null
14812
+ };
14813
+ const result = BobCommandFrontmatterSchema.safeParse(this.frontmatter);
14814
+ if (result.success) return {
14815
+ success: true,
14816
+ error: null
14817
+ };
14818
+ return {
14819
+ success: false,
14820
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
14821
+ };
14822
+ }
14823
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
14824
+ return this.isTargetedByRulesyncCommandDefault({
14825
+ rulesyncCommand,
14826
+ toolTarget: "bob"
14827
+ });
14828
+ }
14829
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
14830
+ const paths = this.getSettablePaths({ global });
14831
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
14832
+ const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
14833
+ const result = BobCommandFrontmatterSchema.safeParse(frontmatter);
14834
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
14835
+ return new BobCommand({
14836
+ outputRoot,
14837
+ relativeDirPath: paths.relativeDirPath,
14838
+ relativeFilePath,
14839
+ frontmatter: result.data,
14840
+ body: content.trim(),
14841
+ validate
14842
+ });
14843
+ }
14844
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
14845
+ return new BobCommand({
14846
+ outputRoot,
14847
+ relativeDirPath,
14848
+ relativeFilePath,
14849
+ frontmatter: { description: "" },
14850
+ body: "",
14851
+ validate: false
14852
+ });
14853
+ }
14854
+ };
14855
+ //#endregion
14514
14856
  //#region src/features/commands/claudecode-command.ts
14515
14857
  const ClaudecodeCommandFrontmatterSchema = zod_mini.z.looseObject({
14516
14858
  description: zod_mini.z.optional(zod_mini.z.string()),
@@ -18237,6 +18579,193 @@ var RovodevPromptsManifest = class extends ToolFile {
18237
18579
  }
18238
18580
  };
18239
18581
  //#endregion
18582
+ //#region src/constants/tabnine-paths.ts
18583
+ const TABNINE_DIR = ".tabnine";
18584
+ const TABNINE_AGENT_DIR_PATH = (0, node_path.join)(TABNINE_DIR, "agent");
18585
+ const TABNINE_RULE_FILE_NAME = "TABNINE.md";
18586
+ (0, node_path.join)(TABNINE_DIR, "guidelines");
18587
+ const TABNINE_IGNORE_FILE_NAME = ".tabnineignore";
18588
+ const TABNINE_SETTINGS_FILE_NAME = "settings.json";
18589
+ const TABNINE_COMMANDS_DIR_PATH = (0, node_path.join)(TABNINE_AGENT_DIR_PATH, "commands");
18590
+ const TABNINE_AGENTS_DIR_PATH = (0, node_path.join)(TABNINE_AGENT_DIR_PATH, "agents");
18591
+ const TABNINE_SKILLS_DIR_PATH = (0, node_path.join)(TABNINE_AGENT_DIR_PATH, "skills");
18592
+ //#endregion
18593
+ //#region src/features/commands/tabnine-command.ts
18594
+ const TabnineCommandFrontmatterSchema = zod_mini.z.looseObject({
18595
+ description: zod_mini.z.optional(zod_mini.z.string()),
18596
+ prompt: zod_mini.z.string()
18597
+ });
18598
+ /**
18599
+ * Translate rulesync universal command syntax (Claude Code compatible) into
18600
+ * Tabnine CLI's native syntax. See docs/reference/command-syntax.md. Tabnine
18601
+ * CLI is a Gemini CLI derivative and documents the same placeholders:
18602
+ * `{{args}}` for the user's arguments and `!{cmd}` for shell injection. Its
18603
+ * `@{path}` file-injection form has no universal counterpart and passes
18604
+ * through untouched in both directions.
18605
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/commands
18606
+ *
18607
+ * Replacement order:
18608
+ * 1. `` !`cmd` `` → `!{cmd}` (backtick shell expansion → brace form)
18609
+ * 2. `$ARGUMENTS` → `{{args}}` (handled after step 1 so that
18610
+ * `` !`echo $ARGUMENTS` `` survives as `!{echo {{args}}}` rather than
18611
+ * requiring two passes).
18612
+ *
18613
+ * `$ARGUMENTS\b` uses a trailing word boundary so `$ARGUMENTSx` and
18614
+ * `$ARGUMENTS_FOO` are left alone, while `$ARGUMENTS-foo` (hyphen is not a
18615
+ * word char) is rewritten. There is no leading anchor, so `prefix$ARGUMENTS`
18616
+ * is rewritten to `prefix{{args}}`.
18617
+ *
18618
+ * Bodies that already contain Tabnine-native forms (`{{args}}` or `!{cmd}`)
18619
+ * are left untouched, which gives us the documented "we do not re-translate
18620
+ * already-Tabnine-native forms" property.
18621
+ */
18622
+ function translateRulesyncBodyToTabnine(body) {
18623
+ return body.replace(/!`([^`\n]+)`/g, "!{$1}").replace(/\$ARGUMENTS\b/g, "{{args}}");
18624
+ }
18625
+ /**
18626
+ * Inverse of {@link translateRulesyncBodyToTabnine}, used when importing a
18627
+ * Tabnine CLI command file back into rulesync's universal syntax.
18628
+ *
18629
+ * Replacement order is intentionally inverted from the forward direction:
18630
+ * 1. `{{args}}` → `$ARGUMENTS` (handled first)
18631
+ * 2. `!{cmd}` → `` !`cmd` `` (handled second, with a non-greedy body
18632
+ * `[^}\n]+?`)
18633
+ *
18634
+ * Doing `{{args}}` first ensures that nested forms like
18635
+ * `!{echo {{args}}}` round-trip back to `` !`echo $ARGUMENTS` `` in a single
18636
+ * pass: the inner `{{args}}` is rewritten to `$ARGUMENTS`, and then the
18637
+ * non-greedy `!{...}` match consumes the smallest possible body.
18638
+ */
18639
+ function translateTabnineBodyToRulesync(body) {
18640
+ return body.replace(/\{\{\s*args\s*\}\}/g, "$ARGUMENTS").replace(/!\{([^}\n]+?)\}/g, "!`$1`");
18641
+ }
18642
+ /**
18643
+ * Tabnine CLI custom slash commands: TOML files with `description` and
18644
+ * `prompt` under `<project>/.tabnine/agent/commands/` (project) and
18645
+ * `~/.tabnine/agent/commands/` (user). The relative path names the command:
18646
+ * `review.toml` → `/review`, `code/review.toml` → `/code:review`.
18647
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/commands
18648
+ */
18649
+ var TabnineCommand = class TabnineCommand extends ToolCommand {
18650
+ frontmatter;
18651
+ body;
18652
+ constructor(params) {
18653
+ super(params);
18654
+ const parsed = this.parseTomlContent(this.fileContent);
18655
+ this.frontmatter = parsed;
18656
+ this.body = parsed.prompt;
18657
+ }
18658
+ static getSettablePaths(_options = {}) {
18659
+ return { relativeDirPath: TABNINE_COMMANDS_DIR_PATH };
18660
+ }
18661
+ parseTomlContent(content) {
18662
+ try {
18663
+ const parsed = (0, smol_toml.parse)(content);
18664
+ const result = TabnineCommandFrontmatterSchema.safeParse(parsed);
18665
+ if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`);
18666
+ return {
18667
+ ...result.data,
18668
+ description: result.data.description
18669
+ };
18670
+ } catch (error) {
18671
+ throw new Error(`Failed to parse TOML command file (${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}): ${formatError(error)}`, { cause: error });
18672
+ }
18673
+ }
18674
+ getBody() {
18675
+ return this.body;
18676
+ }
18677
+ getFrontmatter() {
18678
+ return {
18679
+ description: this.frontmatter.description,
18680
+ prompt: this.frontmatter.prompt
18681
+ };
18682
+ }
18683
+ toRulesyncCommand() {
18684
+ const { description, prompt: _prompt, ...restFields } = this.frontmatter;
18685
+ const rulesyncFrontmatter = {
18686
+ targets: ["tabnine"],
18687
+ description,
18688
+ ...Object.keys(restFields).length > 0 && { tabnine: restFields }
18689
+ };
18690
+ const universalBody = translateTabnineBodyToRulesync(this.body);
18691
+ const fileContent = stringifyFrontmatter(universalBody, rulesyncFrontmatter);
18692
+ return new RulesyncCommand({
18693
+ outputRoot: process.cwd(),
18694
+ frontmatter: rulesyncFrontmatter,
18695
+ body: universalBody,
18696
+ relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
18697
+ relativeFilePath: this.relativeFilePath.replace(/\.toml$/, ".md"),
18698
+ fileContent,
18699
+ validate: true
18700
+ });
18701
+ }
18702
+ static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
18703
+ const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
18704
+ const tabnineFields = rulesyncFrontmatter.tabnine ?? {};
18705
+ const translatedPrompt = typeof tabnineFields.prompt === "string" ? "" : translateRulesyncBodyToTabnine(rulesyncCommand.getBody());
18706
+ const tabnineFrontmatter = {
18707
+ description: rulesyncFrontmatter.description,
18708
+ prompt: translatedPrompt,
18709
+ ...tabnineFields
18710
+ };
18711
+ const tomlObject = {};
18712
+ if (tabnineFrontmatter.description !== void 0) tomlObject.description = tabnineFrontmatter.description;
18713
+ tomlObject.prompt = tabnineFrontmatter.prompt.endsWith("\n") ? tabnineFrontmatter.prompt : `${tabnineFrontmatter.prompt}\n`;
18714
+ const { description: _description, prompt: _prompt, ...extraFields } = tabnineFrontmatter;
18715
+ for (const [key, value] of Object.entries(omitPrototypePollutionKeys(extraFields))) if (value !== void 0) tomlObject[key] = value;
18716
+ const tomlContent = (0, smol_toml.stringify)(tomlObject);
18717
+ const paths = this.getSettablePaths({ global });
18718
+ return new TabnineCommand({
18719
+ outputRoot,
18720
+ relativeDirPath: paths.relativeDirPath,
18721
+ relativeFilePath: rulesyncCommand.getRelativeFilePath().replace(/\.md$/, ".toml"),
18722
+ fileContent: tomlContent,
18723
+ validate
18724
+ });
18725
+ }
18726
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
18727
+ const paths = this.getSettablePaths({ global });
18728
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath));
18729
+ return new TabnineCommand({
18730
+ outputRoot,
18731
+ relativeDirPath: paths.relativeDirPath,
18732
+ relativeFilePath,
18733
+ fileContent,
18734
+ validate
18735
+ });
18736
+ }
18737
+ validate() {
18738
+ try {
18739
+ this.parseTomlContent(this.fileContent);
18740
+ return {
18741
+ success: true,
18742
+ error: null
18743
+ };
18744
+ } catch (error) {
18745
+ return {
18746
+ success: false,
18747
+ error: error instanceof Error ? error : new Error(String(error))
18748
+ };
18749
+ }
18750
+ }
18751
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
18752
+ return this.isTargetedByRulesyncCommandDefault({
18753
+ rulesyncCommand,
18754
+ toolTarget: "tabnine"
18755
+ });
18756
+ }
18757
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
18758
+ return new TabnineCommand({
18759
+ outputRoot,
18760
+ relativeDirPath,
18761
+ relativeFilePath,
18762
+ fileContent: `description = ""
18763
+ prompt = ""`,
18764
+ validate: false
18765
+ });
18766
+ }
18767
+ };
18768
+ //#endregion
18240
18769
  //#region src/features/takt-shared.ts
18241
18770
  /**
18242
18771
  * Shared utilities for all TAKT-* tool file classes.
@@ -18708,6 +19237,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
18708
19237
  matchAdditionalImportsByBasename: true
18709
19238
  }
18710
19239
  }],
19240
+ ["bob", {
19241
+ class: BobCommand,
19242
+ meta: {
19243
+ extension: "md",
19244
+ supportsProject: true,
19245
+ supportsGlobal: true,
19246
+ isSimulated: false,
19247
+ supportsSubdirectory: true
19248
+ }
19249
+ }],
18711
19250
  ["claudecode", {
18712
19251
  class: ClaudecodeCommand,
18713
19252
  meta: {
@@ -18939,6 +19478,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
18939
19478
  supportsSubdirectory: false
18940
19479
  }
18941
19480
  }],
19481
+ ["tabnine", {
19482
+ class: TabnineCommand,
19483
+ meta: {
19484
+ extension: "toml",
19485
+ supportsProject: true,
19486
+ supportsGlobal: true,
19487
+ isSimulated: false,
19488
+ supportsSubdirectory: true
19489
+ }
19490
+ }],
18942
19491
  ["takt", {
18943
19492
  class: TaktCommand,
18944
19493
  meta: {
@@ -20978,6 +21527,135 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
20978
21527
  }
20979
21528
  };
20980
21529
  //#endregion
21530
+ //#region src/features/hooks/bob-hooks.ts
21531
+ const BOB_CONVERTER_CONFIG = {
21532
+ supportedEvents: BOB_HOOK_EVENTS,
21533
+ canonicalToToolEventNames: CANONICAL_TO_BOB_EVENT_NAMES,
21534
+ toolToCanonicalEventNames: BOB_TO_CANONICAL_EVENT_NAMES,
21535
+ projectDirVar: "",
21536
+ noMatcherEvents: /* @__PURE__ */ new Set([
21537
+ "sessionStart",
21538
+ "beforeSubmitPrompt",
21539
+ "stop"
21540
+ ]),
21541
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
21542
+ wildcardMatcherMeansAll: true
21543
+ };
21544
+ /**
21545
+ * Single spelling of the settings.json codec/policy: fail closed on an
21546
+ * unparseable root rather than replacing the user's Bob settings with
21547
+ * generated output.
21548
+ */
21549
+ function parseBobSettings(fileContent, filePath) {
21550
+ return parseSharedConfig({
21551
+ format: "json",
21552
+ fileContent,
21553
+ filePath,
21554
+ invalidRootPolicy: "error"
21555
+ });
21556
+ }
21557
+ /**
21558
+ * IBM Bob lifecycle hooks.
21559
+ *
21560
+ * Hooks live under the top-level `hooks` key of Bob's settings file —
21561
+ * `<project>/.bob/settings.json` (project scope) and
21562
+ * `~/.bob/settings/settings.json` (user scope) — in the Claude-Code shape:
21563
+ * `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{ "type": "command",
21564
+ * "command": "...", "timeout"?: <seconds> }] }] }`. The file also holds
21565
+ * settings rulesync does not own, so generation merges the `hooks` key into it
21566
+ * (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting the file.
21567
+ *
21568
+ * @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
21569
+ */
21570
+ var BobHooks = class BobHooks extends ToolHooks {
21571
+ constructor(params) {
21572
+ super({
21573
+ ...params,
21574
+ fileContent: params.fileContent ?? "{}"
21575
+ });
21576
+ }
21577
+ isDeletable() {
21578
+ return false;
21579
+ }
21580
+ static getSettablePaths({ global = false } = {}) {
21581
+ return {
21582
+ relativeDirPath: global ? BOB_GLOBAL_SETTINGS_DIR_PATH : BOB_DIR,
21583
+ relativeFilePath: BOB_SETTINGS_FILE_NAME
21584
+ };
21585
+ }
21586
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
21587
+ const paths = BobHooks.getSettablePaths({ global });
21588
+ const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
21589
+ return new BobHooks({
21590
+ outputRoot,
21591
+ relativeDirPath: paths.relativeDirPath,
21592
+ relativeFilePath: paths.relativeFilePath,
21593
+ fileContent,
21594
+ validate
21595
+ });
21596
+ }
21597
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
21598
+ const paths = BobHooks.getSettablePaths({ global });
21599
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
21600
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
21601
+ const config = rulesyncHooks.getJson();
21602
+ const hooks = canonicalToToolHooks({
21603
+ config,
21604
+ toolOverrideHooks: config.bob?.hooks,
21605
+ converterConfig: BOB_CONVERTER_CONFIG,
21606
+ logger
21607
+ });
21608
+ const fileContent = applySharedConfigPatch({
21609
+ fileKey: sharedConfigFileKey(paths),
21610
+ feature: "hooks",
21611
+ existingContent,
21612
+ patch: { hooks },
21613
+ filePath,
21614
+ logger
21615
+ });
21616
+ return new BobHooks({
21617
+ outputRoot,
21618
+ relativeDirPath: paths.relativeDirPath,
21619
+ relativeFilePath: paths.relativeFilePath,
21620
+ fileContent,
21621
+ validate
21622
+ });
21623
+ }
21624
+ toRulesyncHooks({ logger } = {}) {
21625
+ const configPath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
21626
+ let settings;
21627
+ try {
21628
+ settings = parseBobSettings(this.getFileContent(), configPath);
21629
+ } catch (error) {
21630
+ throw new Error(`Failed to parse Bob hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
21631
+ }
21632
+ const hooks = toolHooksToCanonical({
21633
+ logger,
21634
+ hooks: settings.hooks,
21635
+ converterConfig: BOB_CONVERTER_CONFIG
21636
+ });
21637
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
21638
+ hooks,
21639
+ overrideKey: "bob"
21640
+ }), null, 2) });
21641
+ }
21642
+ validate() {
21643
+ return {
21644
+ success: true,
21645
+ error: null
21646
+ };
21647
+ }
21648
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
21649
+ return new BobHooks({
21650
+ outputRoot,
21651
+ relativeDirPath,
21652
+ relativeFilePath,
21653
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
21654
+ validate: false
21655
+ });
21656
+ }
21657
+ };
21658
+ //#endregion
20981
21659
  //#region src/features/hooks/preserve-unowned-hook-commands.ts
20982
21660
  /**
20983
21661
  * Read the `hooks` value from a destination JSON file.
@@ -22543,6 +23221,151 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
22543
23221
  }
22544
23222
  };
22545
23223
  //#endregion
23224
+ //#region src/constants/cortexcode-paths.ts
23225
+ const CORTEXCODE_DIR = ".cortex";
23226
+ const CORTEXCODE_GLOBAL_DIR_PATH = (0, node_path.join)(".snowflake", "cortex");
23227
+ const CORTEXCODE_RULE_FILE_NAME = "AGENTS.md";
23228
+ const CORTEXCODE_SETTINGS_FILE_NAME = "settings.json";
23229
+ const CORTEXCODE_GLOBAL_HOOKS_FILE_NAME = "hooks.json";
23230
+ const CORTEXCODE_MCP_FILE_NAME = "mcp.json";
23231
+ const CORTEXCODE_AGENTS_DIR_PATH = (0, node_path.join)(CORTEXCODE_DIR, "agents");
23232
+ const CORTEXCODE_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(CORTEXCODE_GLOBAL_DIR_PATH, "agents");
23233
+ const CORTEXCODE_SKILLS_DIR_PATH = (0, node_path.join)(CORTEXCODE_DIR, "skills");
23234
+ const CORTEXCODE_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(CORTEXCODE_GLOBAL_DIR_PATH, "skills");
23235
+ //#endregion
23236
+ //#region src/features/hooks/cortexcode-hooks.ts
23237
+ const CORTEXCODE_CONVERTER_CONFIG = {
23238
+ supportedEvents: CORTEXCODE_HOOK_EVENTS,
23239
+ canonicalToToolEventNames: CANONICAL_TO_CORTEXCODE_EVENT_NAMES,
23240
+ toolToCanonicalEventNames: CORTEXCODE_TO_CANONICAL_EVENT_NAMES,
23241
+ projectDirVar: "$CORTEX_PROJECT_DIR",
23242
+ prefixDotRelativeCommandsOnly: true,
23243
+ noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
23244
+ supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
23245
+ booleanPassthroughFields: [{
23246
+ canonical: "enabled",
23247
+ tool: "enabled"
23248
+ }]
23249
+ };
23250
+ /**
23251
+ * Single spelling of the settings/hooks codec/policy: fail closed on an
23252
+ * unparseable root rather than replacing the user's Cortex Code settings with
23253
+ * generated output.
23254
+ */
23255
+ function parseCortexcodeSettings(fileContent, filePath) {
23256
+ return parseSharedConfig({
23257
+ format: "json",
23258
+ fileContent,
23259
+ filePath,
23260
+ invalidRootPolicy: "error"
23261
+ });
23262
+ }
23263
+ /**
23264
+ * Snowflake Cortex Code hooks.
23265
+ *
23266
+ * Hooks live under the top-level `hooks` key of `<project>/.cortex/settings.json`
23267
+ * (project scope) and of the dedicated `~/.snowflake/cortex/hooks.json` (user
23268
+ * scope), both in the Claude-Code shape: `{ "<Event>": [{ "matcher"?:
23269
+ * "<regex>", "hooks": [{ "type": "command" | "prompt", ..., "timeout"?:
23270
+ * <seconds>, "enabled"?: <boolean> }] }] }`. The project file also holds
23271
+ * settings rulesync does not own, so generation merges the `hooks` key into
23272
+ * either file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
23273
+ *
23274
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility
23275
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/settings
23276
+ */
23277
+ var CortexcodeHooks = class CortexcodeHooks extends ToolHooks {
23278
+ constructor(params) {
23279
+ super({
23280
+ ...params,
23281
+ fileContent: params.fileContent ?? "{}"
23282
+ });
23283
+ }
23284
+ isDeletable() {
23285
+ return false;
23286
+ }
23287
+ static getSettablePaths({ global = false } = {}) {
23288
+ return global ? {
23289
+ relativeDirPath: CORTEXCODE_GLOBAL_DIR_PATH,
23290
+ relativeFilePath: CORTEXCODE_GLOBAL_HOOKS_FILE_NAME
23291
+ } : {
23292
+ relativeDirPath: CORTEXCODE_DIR,
23293
+ relativeFilePath: CORTEXCODE_SETTINGS_FILE_NAME
23294
+ };
23295
+ }
23296
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
23297
+ const paths = CortexcodeHooks.getSettablePaths({ global });
23298
+ const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
23299
+ return new CortexcodeHooks({
23300
+ outputRoot,
23301
+ relativeDirPath: paths.relativeDirPath,
23302
+ relativeFilePath: paths.relativeFilePath,
23303
+ fileContent,
23304
+ validate
23305
+ });
23306
+ }
23307
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
23308
+ const paths = CortexcodeHooks.getSettablePaths({ global });
23309
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
23310
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
23311
+ const config = rulesyncHooks.getJson();
23312
+ const hooks = canonicalToToolHooks({
23313
+ config,
23314
+ toolOverrideHooks: config.cortexcode?.hooks,
23315
+ converterConfig: CORTEXCODE_CONVERTER_CONFIG,
23316
+ logger
23317
+ });
23318
+ const fileContent = applySharedConfigPatch({
23319
+ fileKey: sharedConfigFileKey(paths),
23320
+ feature: "hooks",
23321
+ existingContent,
23322
+ patch: { hooks },
23323
+ filePath,
23324
+ logger
23325
+ });
23326
+ return new CortexcodeHooks({
23327
+ outputRoot,
23328
+ relativeDirPath: paths.relativeDirPath,
23329
+ relativeFilePath: paths.relativeFilePath,
23330
+ fileContent,
23331
+ validate
23332
+ });
23333
+ }
23334
+ toRulesyncHooks({ logger } = {}) {
23335
+ const configPath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
23336
+ let settings;
23337
+ try {
23338
+ settings = parseCortexcodeSettings(this.getFileContent(), configPath);
23339
+ } catch (error) {
23340
+ throw new Error(`Failed to parse Cortex Code hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
23341
+ }
23342
+ const hooks = toolHooksToCanonical({
23343
+ logger,
23344
+ hooks: settings.hooks,
23345
+ converterConfig: CORTEXCODE_CONVERTER_CONFIG
23346
+ });
23347
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
23348
+ hooks,
23349
+ overrideKey: "cortexcode"
23350
+ }), null, 2) });
23351
+ }
23352
+ validate() {
23353
+ return {
23354
+ success: true,
23355
+ error: null
23356
+ };
23357
+ }
23358
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
23359
+ return new CortexcodeHooks({
23360
+ outputRoot,
23361
+ relativeDirPath,
23362
+ relativeFilePath,
23363
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
23364
+ validate: false
23365
+ });
23366
+ }
23367
+ };
23368
+ //#endregion
22546
23369
  //#region src/features/hooks/cursor-hooks.ts
22547
23370
  var CursorHooks = class CursorHooks extends ToolHooks {
22548
23371
  constructor(params) {
@@ -25770,6 +26593,270 @@ var ReasonixHooks = class ReasonixHooks extends ToolHooks {
25770
26593
  }
25771
26594
  };
25772
26595
  //#endregion
26596
+ //#region src/features/hooks/tabnine-hooks.ts
26597
+ /**
26598
+ * Environment block safe to hand Tabnine for a hook. A tool rebuilds each
26599
+ * entry into `KEY=VALUE` for the spawned process, so a key holding `=`, a
26600
+ * control character or nothing at all names a different variable than it
26601
+ * appears to; such entries are dropped in both directions (with a warning on
26602
+ * export, where the value came from an authored `.rulesync/hooks.*`).
26603
+ */
26604
+ function sanitizeEnv({ env, warn }) {
26605
+ if (env === null || env === void 0 || typeof env !== "object" || Array.isArray(env)) return;
26606
+ const result = {};
26607
+ for (const [key, value] of Object.entries(env)) {
26608
+ const unsafeKey = key === "" || key.includes("=") || CONTROL_CHARS.some((char) => key.includes(char));
26609
+ const unsafeValue = typeof value !== "string" || CONTROL_CHARS.some((char) => value.includes(char));
26610
+ if (unsafeKey || unsafeValue) {
26611
+ warn?.(`Tabnine CLI hook env entry ${JSON.stringify(key)} is not a safe KEY=VALUE pair; skipping it.`);
26612
+ continue;
26613
+ }
26614
+ result[key] = value;
26615
+ }
26616
+ return Object.keys(result).length > 0 ? result : void 0;
26617
+ }
26618
+ /**
26619
+ * Build one Tabnine hook configuration from a canonical command definition.
26620
+ * Canonical `timeout` is seconds (docs/reference/file-formats.md); Tabnine's is
26621
+ * milliseconds.
26622
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks/configuration
26623
+ */
26624
+ function canonicalDefToTabnineHook({ def, warn }) {
26625
+ return {
26626
+ type: "command",
26627
+ ...compact({
26628
+ command: def.command,
26629
+ name: def.name,
26630
+ description: def.description,
26631
+ timeout: typeof def.timeout === "number" && Number.isFinite(def.timeout) ? Math.round(def.timeout * 1e3) : void 0,
26632
+ env: sanitizeEnv({
26633
+ env: def.env,
26634
+ warn
26635
+ })
26636
+ })
26637
+ };
26638
+ }
26639
+ /**
26640
+ * Convert the canonical hooks config to Tabnine's `hooks` map: PascalCase
26641
+ * event names, matcher groups with an optional `sequential` flag, and
26642
+ * command-only hook entries. Tabnine treats an omitted `matcher` as match-all
26643
+ * and compiles tool-event matchers as regular expressions, so the canonical
26644
+ * catch-all `"*"` (not a valid regex) is emitted as no matcher. Commands are
26645
+ * passed through verbatim; Tabnine expands `$TABNINE_PROJECT_DIR` itself.
26646
+ */
26647
+ function canonicalToTabnineHooks({ config, logger }) {
26648
+ const warn = (message) => logger?.warn(message);
26649
+ const supported = new Set(TABNINE_HOOK_EVENTS);
26650
+ const sharedHooks = {};
26651
+ for (const [event, defs] of Object.entries(config.hooks)) if (supported.has(event)) sharedHooks[event] = defs;
26652
+ const effectiveHooks = {
26653
+ ...sharedHooks,
26654
+ ...config.tabnine?.hooks
26655
+ };
26656
+ const tabnine = {};
26657
+ for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
26658
+ const tabnineEventName = lookupOwn({
26659
+ record: CANONICAL_TO_TABNINE_EVENT_NAMES,
26660
+ key: eventName
26661
+ }) ?? eventName;
26662
+ const byMatcher = /* @__PURE__ */ new Map();
26663
+ for (const def of definitions) {
26664
+ if ((def.type ?? "command") !== "command") continue;
26665
+ if (typeof def.command !== "string" || def.command === "") {
26666
+ warn(`Tabnine CLI hook on '${eventName}' has no 'command'; Tabnine would discard it, so it is skipped.`);
26667
+ continue;
26668
+ }
26669
+ const key = def.matcher === void 0 || def.matcher === "*" ? "" : def.matcher;
26670
+ const list = byMatcher.get(key);
26671
+ if (list) list.push(def);
26672
+ else byMatcher.set(key, [def]);
26673
+ }
26674
+ const entries = [];
26675
+ for (const [matcherKey, defs] of byMatcher) {
26676
+ const hooks = defs.map((def) => canonicalDefToTabnineHook({
26677
+ def,
26678
+ warn
26679
+ }));
26680
+ const sequential = defs.some((def) => def.sequential === true);
26681
+ const group = matcherKey ? {
26682
+ matcher: matcherKey,
26683
+ hooks
26684
+ } : { hooks };
26685
+ if (sequential) group.sequential = true;
26686
+ entries.push(group);
26687
+ }
26688
+ if (entries.length > 0) tabnine[tabnineEventName] = entries;
26689
+ }
26690
+ return tabnine;
26691
+ }
26692
+ /**
26693
+ * Tabnine hook configuration as stored in each matcher group's `hooks` array.
26694
+ * `z.looseObject` keeps fields added by future Tabnine releases.
26695
+ */
26696
+ const TabnineHookEntrySchema = zod_mini.z.looseObject({
26697
+ type: zod_mini.z.optional(zod_mini.z.string()),
26698
+ command: zod_mini.z.optional(zod_mini.z.string()),
26699
+ name: zod_mini.z.optional(zod_mini.z.string()),
26700
+ description: zod_mini.z.optional(zod_mini.z.string()),
26701
+ timeout: zod_mini.z.optional(zod_mini.z.number()),
26702
+ env: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string()))
26703
+ });
26704
+ const TabnineMatcherEntrySchema = zod_mini.z.looseObject({
26705
+ matcher: zod_mini.z.optional(zod_mini.z.string()),
26706
+ sequential: zod_mini.z.optional(zod_mini.z.boolean()),
26707
+ hooks: zod_mini.z.optional(zod_mini.z.array(TabnineHookEntrySchema))
26708
+ });
26709
+ function tabnineMatcherEntryToCanonical(entry) {
26710
+ const sequential = entry.sequential === true;
26711
+ const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
26712
+ const defs = [];
26713
+ for (const hook of entry.hooks ?? []) {
26714
+ if (hook.type !== "command" || typeof hook.command !== "string" || hook.command === "") continue;
26715
+ defs.push({
26716
+ type: "command",
26717
+ ...compact({
26718
+ command: hook.command,
26719
+ name: hook.name,
26720
+ description: hook.description,
26721
+ timeout: typeof hook.timeout === "number" && Number.isFinite(hook.timeout) ? hook.timeout / 1e3 : void 0,
26722
+ env: sanitizeEnv({ env: hook.env }),
26723
+ sequential: sequential ? true : void 0,
26724
+ matcher
26725
+ })
26726
+ });
26727
+ }
26728
+ return defs;
26729
+ }
26730
+ /**
26731
+ * Extract the `hooks` map of a Tabnine settings file into canonical form.
26732
+ */
26733
+ function tabnineHooksToCanonical(tabnineHooks) {
26734
+ if (tabnineHooks === null || tabnineHooks === void 0 || typeof tabnineHooks !== "object") return {};
26735
+ const canonical = {};
26736
+ for (const [tabnineEventName, matcherEntries] of Object.entries(tabnineHooks)) {
26737
+ const eventName = lookupOwn({
26738
+ record: TABNINE_TO_CANONICAL_EVENT_NAMES,
26739
+ key: tabnineEventName
26740
+ }) ?? tabnineEventName;
26741
+ if (!Array.isArray(matcherEntries)) continue;
26742
+ const defs = [];
26743
+ for (const rawEntry of matcherEntries) {
26744
+ const parseResult = TabnineMatcherEntrySchema.safeParse(rawEntry);
26745
+ if (!parseResult.success) continue;
26746
+ defs.push(...tabnineMatcherEntryToCanonical(parseResult.data));
26747
+ }
26748
+ if (defs.length > 0) canonical[eventName] = defs;
26749
+ }
26750
+ return canonical;
26751
+ }
26752
+ /**
26753
+ * Fail closed on an unparseable settings root rather than replacing the
26754
+ * user's Tabnine settings with generated output.
26755
+ */
26756
+ function parseTabnineSettings(fileContent, filePath) {
26757
+ return parseSharedConfig({
26758
+ format: "json",
26759
+ fileContent,
26760
+ filePath,
26761
+ invalidRootPolicy: "error"
26762
+ });
26763
+ }
26764
+ /**
26765
+ * Tabnine CLI hooks.
26766
+ *
26767
+ * Hooks live under the top-level `hooks` key of Tabnine's settings file —
26768
+ * `<project>/.tabnine/agent/settings.json` (project scope) and
26769
+ * `~/.tabnine/agent/settings.json` (user scope). The file also carries
26770
+ * `mcpServers`, `tools` and other user-managed settings, so generation merges
26771
+ * the `hooks` key into it (see `SHARED_CONFIG_OWNERSHIP`) instead of
26772
+ * overwriting the file. The sibling `hooksConfig` block (master toggle,
26773
+ * disabled list, notifications) is left to the user.
26774
+ *
26775
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks
26776
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/hooks/configuration
26777
+ */
26778
+ var TabnineHooks = class TabnineHooks extends ToolHooks {
26779
+ constructor(params) {
26780
+ super({
26781
+ ...params,
26782
+ fileContent: params.fileContent ?? "{}"
26783
+ });
26784
+ }
26785
+ isDeletable() {
26786
+ return false;
26787
+ }
26788
+ static getSettablePaths(_options = {}) {
26789
+ return {
26790
+ relativeDirPath: TABNINE_AGENT_DIR_PATH,
26791
+ relativeFilePath: TABNINE_SETTINGS_FILE_NAME
26792
+ };
26793
+ }
26794
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
26795
+ const paths = TabnineHooks.getSettablePaths({ global });
26796
+ const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
26797
+ return new TabnineHooks({
26798
+ outputRoot,
26799
+ relativeDirPath: paths.relativeDirPath,
26800
+ relativeFilePath: paths.relativeFilePath,
26801
+ fileContent,
26802
+ validate
26803
+ });
26804
+ }
26805
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
26806
+ const paths = TabnineHooks.getSettablePaths({ global });
26807
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
26808
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
26809
+ const hooks = canonicalToTabnineHooks({
26810
+ config: rulesyncHooks.getJson(),
26811
+ logger
26812
+ });
26813
+ const fileContent = applySharedConfigPatch({
26814
+ fileKey: sharedConfigFileKey(paths),
26815
+ feature: "hooks",
26816
+ existingContent,
26817
+ patch: { hooks },
26818
+ filePath,
26819
+ logger
26820
+ });
26821
+ return new TabnineHooks({
26822
+ outputRoot,
26823
+ relativeDirPath: paths.relativeDirPath,
26824
+ relativeFilePath: paths.relativeFilePath,
26825
+ fileContent,
26826
+ validate
26827
+ });
26828
+ }
26829
+ toRulesyncHooks() {
26830
+ const configPath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
26831
+ let settings;
26832
+ try {
26833
+ settings = parseTabnineSettings(this.getFileContent(), configPath);
26834
+ } catch (error) {
26835
+ throw new Error(`Failed to parse Tabnine CLI hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
26836
+ }
26837
+ const hooks = tabnineHooksToCanonical(settings.hooks);
26838
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
26839
+ hooks,
26840
+ overrideKey: "tabnine"
26841
+ }), null, 2) });
26842
+ }
26843
+ validate() {
26844
+ return {
26845
+ success: true,
26846
+ error: null
26847
+ };
26848
+ }
26849
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
26850
+ return new TabnineHooks({
26851
+ outputRoot,
26852
+ relativeDirPath,
26853
+ relativeFilePath,
26854
+ fileContent: JSON.stringify({ hooks: {} }, null, 2),
26855
+ validate: false
26856
+ });
26857
+ }
26858
+ };
26859
+ //#endregion
25773
26860
  //#region src/features/hooks/vibe-hooks.ts
25774
26861
  const VIBE_DIR = ".vibe";
25775
26862
  const VIBE_HOOKS_FILE_NAME = "hooks.toml";
@@ -26529,6 +27616,40 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
26529
27616
  supportedHookTypes: ["command"],
26530
27617
  supportsMatcher: true
26531
27618
  }],
27619
+ ["bob", {
27620
+ class: BobHooks,
27621
+ meta: {
27622
+ supportsProject: true,
27623
+ supportsGlobal: true,
27624
+ supportsImport: true
27625
+ },
27626
+ supportedEvents: BOB_HOOK_EVENTS,
27627
+ supportedHookTypes: ["command"],
27628
+ supportsMatcher: true,
27629
+ matcherEvents: ["preToolUse", "postToolUse"]
27630
+ }],
27631
+ ["cortexcode", {
27632
+ class: CortexcodeHooks,
27633
+ meta: {
27634
+ supportsProject: true,
27635
+ supportsGlobal: true,
27636
+ supportsImport: true
27637
+ },
27638
+ supportedEvents: CORTEXCODE_HOOK_EVENTS,
27639
+ supportedHookTypes: ["command", "prompt"],
27640
+ supportsMatcher: true,
27641
+ matcherEvents: [
27642
+ "preToolUse",
27643
+ "postToolUse",
27644
+ "permissionRequest",
27645
+ "sessionStart",
27646
+ "sessionEnd",
27647
+ "preCompact",
27648
+ "subagentStop",
27649
+ "notification",
27650
+ "setup"
27651
+ ]
27652
+ }],
26532
27653
  ["junie", {
26533
27654
  class: JunieHooks,
26534
27655
  meta: {
@@ -26540,6 +27661,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
26540
27661
  supportedHookTypes: ["command"],
26541
27662
  supportsMatcher: true
26542
27663
  }],
27664
+ ["tabnine", {
27665
+ class: TabnineHooks,
27666
+ meta: {
27667
+ supportsProject: true,
27668
+ supportsGlobal: true,
27669
+ supportsImport: true
27670
+ },
27671
+ supportedEvents: TABNINE_HOOK_EVENTS,
27672
+ supportedHookTypes: ["command"],
27673
+ supportsMatcher: true
27674
+ }],
26543
27675
  ["vibe", {
26544
27676
  class: VibeHooks,
26545
27677
  meta: {
@@ -26986,6 +28118,58 @@ var AugmentcodeIgnore = class AugmentcodeIgnore extends ToolIgnore {
26986
28118
  }
26987
28119
  };
26988
28120
  //#endregion
28121
+ //#region src/features/ignore/bob-ignore.ts
28122
+ /**
28123
+ * BobIgnore represents the `.bobignore` file of IBM Bob.
28124
+ *
28125
+ * - File location: workspace root only (`.bobignore`); no user-scope variant
28126
+ * is documented, so the feature is project-only.
28127
+ * - Syntax: the same as `.gitignore`.
28128
+ * - Matching files are hidden from Bob's file reads and listings.
28129
+ *
28130
+ * @see https://bob.ibm.com/docs/ide/configuration/bobignore
28131
+ */
28132
+ var BobIgnore = class BobIgnore extends ToolIgnore {
28133
+ static getSettablePaths() {
28134
+ return {
28135
+ relativeDirPath: ".",
28136
+ relativeFilePath: BOB_IGNORE_FILE_NAME
28137
+ };
28138
+ }
28139
+ toRulesyncIgnore() {
28140
+ return this.toRulesyncIgnoreDefault();
28141
+ }
28142
+ static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
28143
+ const paths = this.getSettablePaths();
28144
+ return new BobIgnore({
28145
+ outputRoot,
28146
+ relativeDirPath: paths.relativeDirPath,
28147
+ relativeFilePath: paths.relativeFilePath,
28148
+ fileContent: rulesyncIgnore.getFileContent()
28149
+ });
28150
+ }
28151
+ static async fromFile({ outputRoot = process.cwd(), validate = true }) {
28152
+ const paths = this.getSettablePaths();
28153
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
28154
+ return new BobIgnore({
28155
+ outputRoot,
28156
+ relativeDirPath: paths.relativeDirPath,
28157
+ relativeFilePath: paths.relativeFilePath,
28158
+ fileContent,
28159
+ validate
28160
+ });
28161
+ }
28162
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
28163
+ return new BobIgnore({
28164
+ outputRoot,
28165
+ relativeDirPath,
28166
+ relativeFilePath,
28167
+ fileContent: "",
28168
+ validate: false
28169
+ });
28170
+ }
28171
+ };
28172
+ //#endregion
26989
28173
  //#region src/features/ignore/claudecode-ignore.ts
26990
28174
  const DEFAULT_FILE_MODE = "shared";
26991
28175
  /**
@@ -28015,6 +29199,60 @@ var RooIgnore = class RooIgnore extends ToolIgnore {
28015
29199
  }
28016
29200
  };
28017
29201
  //#endregion
29202
+ //#region src/features/ignore/tabnine-ignore.ts
29203
+ /**
29204
+ * TabnineIgnore represents the `.tabnineignore` file of Tabnine CLI.
29205
+ *
29206
+ * - File location: project root only (`.tabnineignore`); no user-scope
29207
+ * variant is documented, so the feature is project-only.
29208
+ * - Syntax: the same as `.gitignore`.
29209
+ * - Matching files are excluded from the CLI's file searches and directory
29210
+ * listings while `context.fileFiltering.respectGeminiIgnore` (the default)
29211
+ * is enabled.
29212
+ *
29213
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference
29214
+ */
29215
+ var TabnineIgnore = class TabnineIgnore extends ToolIgnore {
29216
+ static getSettablePaths() {
29217
+ return {
29218
+ relativeDirPath: ".",
29219
+ relativeFilePath: TABNINE_IGNORE_FILE_NAME
29220
+ };
29221
+ }
29222
+ toRulesyncIgnore() {
29223
+ return this.toRulesyncIgnoreDefault();
29224
+ }
29225
+ static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
29226
+ const paths = this.getSettablePaths();
29227
+ return new TabnineIgnore({
29228
+ outputRoot,
29229
+ relativeDirPath: paths.relativeDirPath,
29230
+ relativeFilePath: paths.relativeFilePath,
29231
+ fileContent: rulesyncIgnore.getFileContent()
29232
+ });
29233
+ }
29234
+ static async fromFile({ outputRoot = process.cwd(), validate = true }) {
29235
+ const paths = this.getSettablePaths();
29236
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
29237
+ return new TabnineIgnore({
29238
+ outputRoot,
29239
+ relativeDirPath: paths.relativeDirPath,
29240
+ relativeFilePath: paths.relativeFilePath,
29241
+ fileContent,
29242
+ validate
29243
+ });
29244
+ }
29245
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
29246
+ return new TabnineIgnore({
29247
+ outputRoot,
29248
+ relativeDirPath,
29249
+ relativeFilePath,
29250
+ fileContent: "",
29251
+ validate: false
29252
+ });
29253
+ }
29254
+ };
29255
+ //#endregion
28018
29256
  //#region src/features/ignore/vibe-ignore.ts
28019
29257
  var VibeIgnore = class VibeIgnore extends ToolIgnore {
28020
29258
  static getSettablePaths() {
@@ -28220,6 +29458,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
28220
29458
  ["aiassistant", { class: AiassistantIgnore }],
28221
29459
  ["antigravity-cli", { class: AntigravityCliIgnore }],
28222
29460
  ["augmentcode", { class: AugmentcodeIgnore }],
29461
+ ["bob", { class: BobIgnore }],
28223
29462
  ["claudecode", { class: ClaudecodeIgnore }],
28224
29463
  ["claudecode-legacy", { class: ClaudecodeIgnore }],
28225
29464
  ["cline", { class: ClineIgnore }],
@@ -28236,6 +29475,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
28236
29475
  ["roo", { class: RooIgnore }],
28237
29476
  ["zoocode", { class: RooIgnore }],
28238
29477
  ["devin", { class: DevinIgnore }],
29478
+ ["tabnine", { class: TabnineIgnore }],
28239
29479
  ["vibe", { class: VibeIgnore }],
28240
29480
  ["warp", { class: WarpIgnore }],
28241
29481
  ["zed", { class: ZedIgnore }]
@@ -28970,6 +30210,343 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
28970
30210
  }
28971
30211
  };
28972
30212
  //#endregion
30213
+ //#region src/features/mcp/mcp-transport.ts
30214
+ /**
30215
+ * A server that names no way to be reached at all. It is what a Kilo bare
30216
+ * `{"enabled": <bool>}` entry imports as — a switch for a server another config
30217
+ * layer defines, which only Kilo's config spells this way. Every other tool's
30218
+ * config defines the servers it lists, so there the entry has no equivalent and
30219
+ * must be skipped rather than written as a server with an empty command.
30220
+ */
30221
+ function declaresNoTransport(serverConfig) {
30222
+ return serverConfig.type === void 0 && serverConfig.transport === void 0 && serverConfig.command === void 0 && serverConfig.url === void 0 && serverConfig.httpUrl === void 0;
30223
+ }
30224
+ const REMOTE_TRANSPORTS = /* @__PURE__ */ new Set([
30225
+ "sse",
30226
+ "http",
30227
+ "streamable-http",
30228
+ "ws"
30229
+ ]);
30230
+ /**
30231
+ * Whether the server speaks over the network rather than over a spawned
30232
+ * process. `httpUrl`, the Claude-specific alias several adapters accept, counts
30233
+ * as much as `url` does, and so does `transport`, the alias `type` has
30234
+ * everywhere in the canonical config.
30235
+ */
30236
+ function isRemoteMcpServer(serverConfig) {
30237
+ return REMOTE_TRANSPORTS.has(serverConfig.type ?? "") || REMOTE_TRANSPORTS.has(serverConfig.transport ?? "") || serverConfig.url !== void 0 || serverConfig.httpUrl !== void 0;
30238
+ }
30239
+ /** The URL a remote server is reached at, or `undefined` if it states none. */
30240
+ function resolveRemoteMcpUrl(serverConfig) {
30241
+ return serverConfig.url || serverConfig.httpUrl || void 0;
30242
+ }
30243
+ /** The `command` array a `local` server is spawned with, `args` merged in. */
30244
+ function resolveLocalMcpCommand(serverConfig) {
30245
+ const commandArray = [];
30246
+ if (serverConfig.command) {
30247
+ if (Array.isArray(serverConfig.command)) commandArray.push(...serverConfig.command);
30248
+ else commandArray.push(serverConfig.command);
30249
+ }
30250
+ if (serverConfig.args) commandArray.push(...serverConfig.args);
30251
+ return commandArray;
30252
+ }
30253
+ /**
30254
+ * A server that names a transport but carries nothing to reach it — a `type`
30255
+ * with no `command`, an `http` with no `url` — has no form in a config that
30256
+ * spells the transport out. Writing `{type: "local", command: []}` for it would
30257
+ * give the tool a server it cannot start and the importer a file it cannot read
30258
+ * back, so it is skipped out loud instead.
30259
+ */
30260
+ function warnAndSkipMcpServer({ toolName, serverName, reason, logger }) {
30261
+ logger?.warn(`${toolName} MCP: skipping "${serverName}" because it declares ${reason}.`);
30262
+ return null;
30263
+ }
30264
+ /**
30265
+ * The `tools` map Kilo and OpenCode keep beside `mcp` is keyed `<server>_<tool>`
30266
+ * and reaches servers `mcp` does not list — ones the global config or a
30267
+ * marketplace defines — so entries naming no listed
30268
+ * server are filters that would be dropped by a walk over `mcp` alone, and
30269
+ * deleted from the file on the next generate. They come back as transport-less
30270
+ * servers carrying nothing but the filters.
30271
+ *
30272
+ * The split is at the first `_`, which is where the write side joined them. A
30273
+ * server whose own name contains one is reconstructed under a shorter name, but
30274
+ * the key it is written back as is character-for-character the one that was
30275
+ * read, so Kilo sees exactly what it saw before.
30276
+ */
30277
+ function orphanMcpToolFiltersToRulesync(servers, tools) {
30278
+ const orphans = {};
30279
+ for (const [toolName, enabled] of Object.entries(tools ?? {})) {
30280
+ if (Object.keys(servers).some((serverName) => toolName.startsWith(`${serverName}_`))) continue;
30281
+ const separator = toolName.indexOf("_");
30282
+ if (separator <= 0 || separator === toolName.length - 1) continue;
30283
+ const serverName = toolName.slice(0, separator);
30284
+ const toolSuffix = toolName.slice(separator + 1);
30285
+ if (!Object.hasOwn(orphans, serverName)) orphans[serverName] = {};
30286
+ const server = orphans[serverName];
30287
+ if (enabled) server.enabledTools = [...server.enabledTools ?? [], toolSuffix];
30288
+ else server.disabledTools = [...server.disabledTools ?? [], toolSuffix];
30289
+ }
30290
+ return orphans;
30291
+ }
30292
+ /**
30293
+ * Split imported servers into the ones every tool can use and the ones only the
30294
+ * importing tool can. A server with no transport is a Kilo/OpenCode idea — a
30295
+ * switch for a server another config layer defines, or a filter for one — and
30296
+ * nothing else knows what to do with it: written to `.mcp.json` and the rest,
30297
+ * it is an entry no tool can start. It goes into the tool-scoped
30298
+ * `{tool}.mcpServers` block instead, which only that tool reads.
30299
+ */
30300
+ function splitMcpServersByTransport(servers) {
30301
+ const shared = {};
30302
+ const toolOnly = {};
30303
+ for (const [serverName, serverConfig] of Object.entries(servers)) if (declaresNoTransport(serverConfig)) toolOnly[serverName] = serverConfig;
30304
+ else shared[serverName] = serverConfig;
30305
+ return {
30306
+ shared,
30307
+ toolOnly
30308
+ };
30309
+ }
30310
+ //#endregion
30311
+ //#region src/features/mcp/bob-mcp.ts
30312
+ /**
30313
+ * Parse a Bob MCP file, failing closed on malformed JSON or a non-object root
30314
+ * (`null`, an array, a scalar) rather than spreading whatever came back into
30315
+ * the regenerated file.
30316
+ */
30317
+ function parseBobMcpConfig({ fileContent, relativePath }) {
30318
+ let parsed;
30319
+ try {
30320
+ parsed = JSON.parse(fileContent);
30321
+ } catch (error) {
30322
+ throw new Error(`Failed to parse Bob MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
30323
+ }
30324
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Bob MCP config at ${relativePath}: expected a JSON object at the root`);
30325
+ return parsed;
30326
+ }
30327
+ /** The `type` Bob IDE writes for a streamable HTTP server. */
30328
+ const BOB_STREAMABLE_HTTP_TYPE = "streamable-http";
30329
+ /**
30330
+ * The remote transport Bob IDE reads a server as, spelled the way it writes
30331
+ * it: `streamable-http` needs an explicit `type`, while SSE (legacy) is a bare
30332
+ * `url` with no `type`. `http` is the canonical rulesync alias for streamable
30333
+ * HTTP and a bare `url` defaults to it, streamable HTTP being the current MCP
30334
+ * transport. A `ws(s)://` URL or any other stated transport is something Bob
30335
+ * cannot reach, so `undefined` tells the caller to skip the server.
30336
+ * @see https://bob.ibm.com/docs/ide/configuration/mcp/mcp-in-bob
30337
+ */
30338
+ function asBobRemoteType(stated, url) {
30339
+ if (stated === "sse") return "sse";
30340
+ if (stated === "http" || stated === BOB_STREAMABLE_HTTP_TYPE) return BOB_STREAMABLE_HTTP_TYPE;
30341
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : BOB_STREAMABLE_HTTP_TYPE;
30342
+ }
30343
+ /**
30344
+ * Convert the canonical server map to the shape Bob IDE documents for
30345
+ * `.bob/mcp.json`: a stdio server carries `command` (plus `args`), a
30346
+ * streamable HTTP server carries `type: "streamable-http"` and `url`, and an
30347
+ * SSE server carries a bare `url`. The canonical `transport` alias and the
30348
+ * Claude-style `httpUrl` alias are folded into `type`/`url`; `env`, `cwd`,
30349
+ * `headers`, `timeout`, `alwaysAllow` and `disabled` pass through unchanged,
30350
+ * as Bob documents all of them.
30351
+ *
30352
+ * Bob Shell documents the same file with an `httpURL` key instead of
30353
+ * `type` + `url` for streamable HTTP. rulesync writes the IDE spelling (the two
30354
+ * products share the project file, and the IDE is the one whose global file
30355
+ * rulesync targets) and accepts the Shell spelling on import.
30356
+ *
30357
+ * A server Bob cannot start or reach — no transport at all, a remote transport
30358
+ * without a URL, a WebSocket URL, or a stdio entry without a command — is
30359
+ * skipped with a warning rather than written in a broken form.
30360
+ * @see https://bob.ibm.com/docs/ide/configuration/mcp/mcp-in-bob
30361
+ * @see https://bob.ibm.com/docs/shell/configuration/mcp/mcp-bobshell
30362
+ */
30363
+ function convertToBobFormat(mcpServers, logger) {
30364
+ const result = {};
30365
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
30366
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
30367
+ if (declaresNoTransport(serverConfig)) {
30368
+ warnAndSkipMcpServer({
30369
+ toolName: "Bob",
30370
+ serverName,
30371
+ reason: "no transport",
30372
+ logger
30373
+ });
30374
+ continue;
30375
+ }
30376
+ const { type, transport, url: _url, httpUrl: _httpUrl, command: _command, args: _args, ...rest } = serverConfig;
30377
+ const converted = {};
30378
+ if (isRemoteMcpServer(serverConfig)) {
30379
+ const url = resolveRemoteMcpUrl(serverConfig);
30380
+ if (!url) {
30381
+ warnAndSkipMcpServer({
30382
+ toolName: "Bob",
30383
+ serverName,
30384
+ reason: "a remote transport without a url",
30385
+ logger
30386
+ });
30387
+ continue;
30388
+ }
30389
+ const stated = type ?? transport;
30390
+ const remoteType = asBobRemoteType(stated, url);
30391
+ if (remoteType === void 0) {
30392
+ warnAndSkipMcpServer({
30393
+ toolName: "Bob",
30394
+ serverName,
30395
+ 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)`,
30396
+ logger
30397
+ });
30398
+ continue;
30399
+ }
30400
+ if (remoteType === BOB_STREAMABLE_HTTP_TYPE) converted.type = BOB_STREAMABLE_HTTP_TYPE;
30401
+ converted.url = url;
30402
+ } else {
30403
+ const [command, ...args] = resolveLocalMcpCommand(serverConfig);
30404
+ if (!command) {
30405
+ warnAndSkipMcpServer({
30406
+ toolName: "Bob",
30407
+ serverName,
30408
+ reason: "a stdio transport without a command",
30409
+ logger
30410
+ });
30411
+ continue;
30412
+ }
30413
+ converted.command = command;
30414
+ if (args.length > 0) converted.args = args;
30415
+ }
30416
+ for (const [key, value] of Object.entries(rest)) {
30417
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
30418
+ converted[key] = (key === "env" || key === "headers") && isRecord$1(value) ? omitPrototypePollutionKeys(value) : value;
30419
+ }
30420
+ result[serverName] = converted;
30421
+ }
30422
+ return result;
30423
+ }
30424
+ /**
30425
+ * Convert Bob's server map back to the canonical shape. The IDE spelling
30426
+ * (`type: "streamable-http"` + `url`) is already canonical and passes through;
30427
+ * the Bob Shell spelling `httpURL` becomes `url` with `type: "http"`; a bare
30428
+ * `url` is an SSE server in Bob, so it gains `type: "sse"` to keep that reading
30429
+ * on the next generate.
30430
+ */
30431
+ function convertFromBobFormat(mcpServers) {
30432
+ if (!isMcpServers(mcpServers)) return {};
30433
+ const result = {};
30434
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
30435
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
30436
+ const converted = {};
30437
+ let httpURL;
30438
+ for (const [key, value] of Object.entries(serverConfig)) {
30439
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
30440
+ if (key === "httpURL") {
30441
+ if (typeof value === "string") httpURL = value;
30442
+ continue;
30443
+ }
30444
+ converted[key] = value;
30445
+ }
30446
+ if (httpURL !== void 0) {
30447
+ converted.url = httpURL;
30448
+ converted.type = "http";
30449
+ } else if (typeof converted.url === "string" && converted.type === void 0) converted.type = "sse";
30450
+ result[serverName] = converted;
30451
+ }
30452
+ return result;
30453
+ }
30454
+ /**
30455
+ * IBM Bob MCP configuration.
30456
+ *
30457
+ * Bob IDE reads `mcpServers` from `<project>/.bob/mcp.json` (project scope)
30458
+ * and `~/.bob/mcp.json` (user scope); the project entry wins when both define
30459
+ * the same server name. Both files are dedicated to MCP. The project one is
30460
+ * deletable; the user one is created and edited by Bob IDE's own MCP settings
30461
+ * UI, so `--delete` leaves it in place rather than removing a file the user
30462
+ * did not create through rulesync. (Bob Shell reads its user-scoped servers
30463
+ * from `~/.bob/mcp_settings.json` instead, which rulesync does not write.)
30464
+ *
30465
+ * @see https://bob.ibm.com/docs/ide/configuration/mcp/mcp-in-bob
30466
+ * @see https://bob.ibm.com/docs/shell/configuration/mcp/mcp-bobshell
30467
+ */
30468
+ var BobMcp = class BobMcp extends ToolMcp {
30469
+ json;
30470
+ constructor(params) {
30471
+ super(params);
30472
+ this.json = this.fileContent === void 0 ? {} : parseBobMcpConfig({
30473
+ fileContent: this.fileContent,
30474
+ relativePath: (0, node_path.join)(this.relativeDirPath, this.relativeFilePath)
30475
+ });
30476
+ }
30477
+ getJson() {
30478
+ return this.json;
30479
+ }
30480
+ isDeletable() {
30481
+ return !this.global;
30482
+ }
30483
+ static getSettablePaths(_options = {}) {
30484
+ return {
30485
+ relativeDirPath: BOB_DIR,
30486
+ relativeFilePath: BOB_MCP_FILE_NAME
30487
+ };
30488
+ }
30489
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
30490
+ const paths = this.getSettablePaths({ global });
30491
+ const json = parseBobMcpConfig({
30492
+ fileContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
30493
+ relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
30494
+ });
30495
+ const newJson = {
30496
+ ...json,
30497
+ mcpServers: json.mcpServers ?? {}
30498
+ };
30499
+ return new BobMcp({
30500
+ outputRoot,
30501
+ relativeDirPath: paths.relativeDirPath,
30502
+ relativeFilePath: paths.relativeFilePath,
30503
+ fileContent: JSON.stringify(newJson, null, 2),
30504
+ validate,
30505
+ global
30506
+ });
30507
+ }
30508
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
30509
+ const paths = this.getSettablePaths({ global });
30510
+ const json = parseBobMcpConfig({
30511
+ fileContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2),
30512
+ relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
30513
+ });
30514
+ const mcpServers = convertToBobFormat(rulesyncMcp.getMcpServers(), logger);
30515
+ const bobConfig = {
30516
+ ...json,
30517
+ mcpServers
30518
+ };
30519
+ return new BobMcp({
30520
+ outputRoot,
30521
+ relativeDirPath: paths.relativeDirPath,
30522
+ relativeFilePath: paths.relativeFilePath,
30523
+ fileContent: JSON.stringify(bobConfig, null, 2),
30524
+ validate,
30525
+ global
30526
+ });
30527
+ }
30528
+ toRulesyncMcp() {
30529
+ const mcpServers = convertFromBobFormat(this.json.mcpServers);
30530
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
30531
+ }
30532
+ validate() {
30533
+ return {
30534
+ success: true,
30535
+ error: null
30536
+ };
30537
+ }
30538
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
30539
+ return new BobMcp({
30540
+ outputRoot,
30541
+ relativeDirPath,
30542
+ relativeFilePath,
30543
+ fileContent: "{}",
30544
+ validate: false,
30545
+ global
30546
+ });
30547
+ }
30548
+ };
30549
+ //#endregion
28973
30550
  //#region src/features/mcp/claudecode-mcp.ts
28974
30551
  var ClaudecodeMcp = class ClaudecodeMcp extends ToolMcp {
28975
30552
  json;
@@ -29677,104 +31254,6 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
29677
31254
  }
29678
31255
  };
29679
31256
  //#endregion
29680
- //#region src/features/mcp/mcp-transport.ts
29681
- /**
29682
- * A server that names no way to be reached at all. It is what a Kilo bare
29683
- * `{"enabled": <bool>}` entry imports as — a switch for a server another config
29684
- * layer defines, which only Kilo's config spells this way. Every other tool's
29685
- * config defines the servers it lists, so there the entry has no equivalent and
29686
- * must be skipped rather than written as a server with an empty command.
29687
- */
29688
- function declaresNoTransport(serverConfig) {
29689
- return serverConfig.type === void 0 && serverConfig.transport === void 0 && serverConfig.command === void 0 && serverConfig.url === void 0 && serverConfig.httpUrl === void 0;
29690
- }
29691
- const REMOTE_TRANSPORTS = /* @__PURE__ */ new Set([
29692
- "sse",
29693
- "http",
29694
- "streamable-http",
29695
- "ws"
29696
- ]);
29697
- /**
29698
- * Whether the server speaks over the network rather than over a spawned
29699
- * process. `httpUrl`, the Claude-specific alias several adapters accept, counts
29700
- * as much as `url` does, and so does `transport`, the alias `type` has
29701
- * everywhere in the canonical config.
29702
- */
29703
- function isRemoteMcpServer(serverConfig) {
29704
- return REMOTE_TRANSPORTS.has(serverConfig.type ?? "") || REMOTE_TRANSPORTS.has(serverConfig.transport ?? "") || serverConfig.url !== void 0 || serverConfig.httpUrl !== void 0;
29705
- }
29706
- /** The URL a remote server is reached at, or `undefined` if it states none. */
29707
- function resolveRemoteMcpUrl(serverConfig) {
29708
- return serverConfig.url || serverConfig.httpUrl || void 0;
29709
- }
29710
- /** The `command` array a `local` server is spawned with, `args` merged in. */
29711
- function resolveLocalMcpCommand(serverConfig) {
29712
- const commandArray = [];
29713
- if (serverConfig.command) {
29714
- if (Array.isArray(serverConfig.command)) commandArray.push(...serverConfig.command);
29715
- else commandArray.push(serverConfig.command);
29716
- }
29717
- if (serverConfig.args) commandArray.push(...serverConfig.args);
29718
- return commandArray;
29719
- }
29720
- /**
29721
- * A server that names a transport but carries nothing to reach it — a `type`
29722
- * with no `command`, an `http` with no `url` — has no form in a config that
29723
- * spells the transport out. Writing `{type: "local", command: []}` for it would
29724
- * give the tool a server it cannot start and the importer a file it cannot read
29725
- * back, so it is skipped out loud instead.
29726
- */
29727
- function warnAndSkipMcpServer({ toolName, serverName, reason, logger }) {
29728
- logger?.warn(`${toolName} MCP: skipping "${serverName}" because it declares ${reason}.`);
29729
- return null;
29730
- }
29731
- /**
29732
- * The `tools` map Kilo and OpenCode keep beside `mcp` is keyed `<server>_<tool>`
29733
- * and reaches servers `mcp` does not list — ones the global config or a
29734
- * marketplace defines — so entries naming no listed
29735
- * server are filters that would be dropped by a walk over `mcp` alone, and
29736
- * deleted from the file on the next generate. They come back as transport-less
29737
- * servers carrying nothing but the filters.
29738
- *
29739
- * The split is at the first `_`, which is where the write side joined them. A
29740
- * server whose own name contains one is reconstructed under a shorter name, but
29741
- * the key it is written back as is character-for-character the one that was
29742
- * read, so Kilo sees exactly what it saw before.
29743
- */
29744
- function orphanMcpToolFiltersToRulesync(servers, tools) {
29745
- const orphans = {};
29746
- for (const [toolName, enabled] of Object.entries(tools ?? {})) {
29747
- if (Object.keys(servers).some((serverName) => toolName.startsWith(`${serverName}_`))) continue;
29748
- const separator = toolName.indexOf("_");
29749
- if (separator <= 0 || separator === toolName.length - 1) continue;
29750
- const serverName = toolName.slice(0, separator);
29751
- const toolSuffix = toolName.slice(separator + 1);
29752
- if (!Object.hasOwn(orphans, serverName)) orphans[serverName] = {};
29753
- const server = orphans[serverName];
29754
- if (enabled) server.enabledTools = [...server.enabledTools ?? [], toolSuffix];
29755
- else server.disabledTools = [...server.disabledTools ?? [], toolSuffix];
29756
- }
29757
- return orphans;
29758
- }
29759
- /**
29760
- * Split imported servers into the ones every tool can use and the ones only the
29761
- * importing tool can. A server with no transport is a Kilo/OpenCode idea — a
29762
- * switch for a server another config layer defines, or a filter for one — and
29763
- * nothing else knows what to do with it: written to `.mcp.json` and the rest,
29764
- * it is an entry no tool can start. It goes into the tool-scoped
29765
- * `{tool}.mcpServers` block instead, which only that tool reads.
29766
- */
29767
- function splitMcpServersByTransport(servers) {
29768
- const shared = {};
29769
- const toolOnly = {};
29770
- for (const [serverName, serverConfig] of Object.entries(servers)) if (declaresNoTransport(serverConfig)) toolOnly[serverName] = serverConfig;
29771
- else shared[serverName] = serverConfig;
29772
- return {
29773
- shared,
29774
- toolOnly
29775
- };
29776
- }
29777
- //#endregion
29778
31257
  //#region src/features/mcp/copilotcli-mcp.ts
29779
31258
  /**
29780
31259
  * Reached over WebSocket, which Copilot CLI has no transport for. The url is
@@ -30043,6 +31522,222 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
30043
31522
  }
30044
31523
  };
30045
31524
  //#endregion
31525
+ //#region src/features/mcp/cortexcode-mcp.ts
31526
+ const CORTEXCODE_GLOBAL_ONLY_MESSAGE = "Snowflake Cortex Code MCP is global-only; use --global to sync ~/.snowflake/cortex/mcp.json";
31527
+ /**
31528
+ * Parse a Cortex Code MCP file, failing closed on malformed JSON or a
31529
+ * non-object root (`null`, an array, a scalar) rather than spreading whatever
31530
+ * came back into the regenerated file.
31531
+ */
31532
+ function parseCortexcodeMcpConfig({ fileContent, relativePath }) {
31533
+ let parsed;
31534
+ try {
31535
+ parsed = JSON.parse(fileContent);
31536
+ } catch (error) {
31537
+ throw new Error(`Failed to parse Cortex Code MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
31538
+ }
31539
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Cortex Code MCP config at ${relativePath}: expected a JSON object at the root`);
31540
+ return parsed;
31541
+ }
31542
+ /**
31543
+ * The remote transport Cortex Code reads a server as. Cortex Code documents
31544
+ * exactly three transports — `stdio`, `http` and `sse` — each spelled out in
31545
+ * an explicit `type` key. `http` is also the canonical rulesync spelling, and
31546
+ * a bare `url` defaults to it, streamable HTTP being the current MCP
31547
+ * transport. `streamable-http` is folded into `http`. A `ws(s)://` URL or any
31548
+ * other stated transport is something Cortex Code cannot reach, so
31549
+ * `undefined` tells the caller to skip the server.
31550
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility#transport-types
31551
+ */
31552
+ function asCortexcodeRemoteType(stated, url) {
31553
+ if (stated === "sse") return "sse";
31554
+ if (stated === "http" || stated === "streamable-http") return "http";
31555
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
31556
+ }
31557
+ /**
31558
+ * Convert the canonical server map to the shape Cortex Code documents for
31559
+ * `~/.snowflake/cortex/mcp.json`: every server carries an explicit `type`
31560
+ * (`stdio`, `http` or `sse`); a stdio server carries `command` (plus `args`)
31561
+ * and a remote server carries `url`. The canonical `transport` alias and the
31562
+ * Claude-style `httpUrl` alias are folded into `type`/`url`; `env`, `headers`,
31563
+ * `oauth` and `timeout` pass through, as Cortex Code documents all of them.
31564
+ *
31565
+ * A server Cortex Code cannot start or reach — no transport at all, a remote
31566
+ * transport without a URL, a WebSocket URL, or a stdio entry without a
31567
+ * command — is skipped with a warning rather than written in a broken form.
31568
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility#mcp-configuration
31569
+ */
31570
+ function convertToCortexcodeFormat(mcpServers, logger) {
31571
+ const result = {};
31572
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
31573
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
31574
+ if (declaresNoTransport(serverConfig)) {
31575
+ warnAndSkipMcpServer({
31576
+ toolName: "Cortex Code",
31577
+ serverName,
31578
+ reason: "no transport",
31579
+ logger
31580
+ });
31581
+ continue;
31582
+ }
31583
+ const { type, transport, url: _url, httpUrl: _httpUrl, command: _command, args: _args, ...rest } = serverConfig;
31584
+ const converted = {};
31585
+ if (isRemoteMcpServer(serverConfig)) {
31586
+ const url = resolveRemoteMcpUrl(serverConfig);
31587
+ if (!url) {
31588
+ warnAndSkipMcpServer({
31589
+ toolName: "Cortex Code",
31590
+ serverName,
31591
+ reason: "a remote transport without a url",
31592
+ logger
31593
+ });
31594
+ continue;
31595
+ }
31596
+ const stated = type ?? transport;
31597
+ const remoteType = asCortexcodeRemoteType(stated, url);
31598
+ if (remoteType === void 0) {
31599
+ warnAndSkipMcpServer({
31600
+ toolName: "Cortex Code",
31601
+ serverName,
31602
+ 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)`,
31603
+ logger
31604
+ });
31605
+ continue;
31606
+ }
31607
+ converted.type = remoteType;
31608
+ converted.url = url;
31609
+ } else {
31610
+ const [command, ...args] = resolveLocalMcpCommand(serverConfig);
31611
+ if (!command) {
31612
+ warnAndSkipMcpServer({
31613
+ toolName: "Cortex Code",
31614
+ serverName,
31615
+ reason: "a stdio transport without a command",
31616
+ logger
31617
+ });
31618
+ continue;
31619
+ }
31620
+ converted.type = "stdio";
31621
+ converted.command = command;
31622
+ if (args.length > 0) converted.args = args;
31623
+ }
31624
+ for (const [key, value] of Object.entries(rest)) {
31625
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
31626
+ converted[key] = (key === "env" || key === "headers") && isRecord$1(value) ? omitPrototypePollutionKeys(value) : value;
31627
+ }
31628
+ result[serverName] = converted;
31629
+ }
31630
+ return result;
31631
+ }
31632
+ /**
31633
+ * Convert Cortex Code's server map back to the canonical shape. The
31634
+ * documented spelling (`type` + `command`/`url`) is already canonical, so
31635
+ * entries pass through with only prototype-pollution keys dropped.
31636
+ */
31637
+ function convertFromCortexcodeFormat(mcpServers) {
31638
+ if (!isMcpServers(mcpServers)) return {};
31639
+ const result = {};
31640
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
31641
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
31642
+ result[serverName] = omitPrototypePollutionKeys(serverConfig);
31643
+ }
31644
+ return result;
31645
+ }
31646
+ /**
31647
+ * Snowflake Cortex Code MCP configuration.
31648
+ *
31649
+ * Cortex Code reads `mcpServers` only from the single user-scoped file
31650
+ * `~/.snowflake/cortex/mcp.json`; no project-scoped MCP location is
31651
+ * documented, so the target is global-only. The file is created and edited by
31652
+ * `cortex mcp add` as well, so `--delete` leaves it in place rather than
31653
+ * removing a file the user did not create through rulesync.
31654
+ *
31655
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility#mcp-configuration
31656
+ */
31657
+ var CortexcodeMcp = class CortexcodeMcp extends ToolMcp {
31658
+ json;
31659
+ constructor(params) {
31660
+ super(params);
31661
+ this.json = this.fileContent === void 0 ? {} : parseCortexcodeMcpConfig({
31662
+ fileContent: this.fileContent,
31663
+ relativePath: (0, node_path.join)(this.relativeDirPath, this.relativeFilePath)
31664
+ });
31665
+ }
31666
+ getJson() {
31667
+ return this.json;
31668
+ }
31669
+ isDeletable() {
31670
+ return false;
31671
+ }
31672
+ static getSettablePaths(_options = {}) {
31673
+ return {
31674
+ relativeDirPath: CORTEXCODE_GLOBAL_DIR_PATH,
31675
+ relativeFilePath: CORTEXCODE_MCP_FILE_NAME
31676
+ };
31677
+ }
31678
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
31679
+ if (!global) throw new Error(CORTEXCODE_GLOBAL_ONLY_MESSAGE);
31680
+ const paths = this.getSettablePaths({ global });
31681
+ const json = parseCortexcodeMcpConfig({
31682
+ fileContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
31683
+ relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
31684
+ });
31685
+ const newJson = {
31686
+ ...json,
31687
+ mcpServers: json.mcpServers ?? {}
31688
+ };
31689
+ return new CortexcodeMcp({
31690
+ outputRoot,
31691
+ relativeDirPath: paths.relativeDirPath,
31692
+ relativeFilePath: paths.relativeFilePath,
31693
+ fileContent: JSON.stringify(newJson, null, 2),
31694
+ validate,
31695
+ global
31696
+ });
31697
+ }
31698
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
31699
+ if (!global) throw new Error(CORTEXCODE_GLOBAL_ONLY_MESSAGE);
31700
+ const paths = this.getSettablePaths({ global });
31701
+ const json = parseCortexcodeMcpConfig({
31702
+ fileContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2),
31703
+ relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
31704
+ });
31705
+ const mcpServers = convertToCortexcodeFormat(rulesyncMcp.getMcpServers(), logger);
31706
+ const cortexcodeConfig = {
31707
+ ...json,
31708
+ mcpServers
31709
+ };
31710
+ return new CortexcodeMcp({
31711
+ outputRoot,
31712
+ relativeDirPath: paths.relativeDirPath,
31713
+ relativeFilePath: paths.relativeFilePath,
31714
+ fileContent: JSON.stringify(cortexcodeConfig, null, 2),
31715
+ validate,
31716
+ global
31717
+ });
31718
+ }
31719
+ toRulesyncMcp() {
31720
+ const mcpServers = convertFromCortexcodeFormat(this.json.mcpServers);
31721
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
31722
+ }
31723
+ validate() {
31724
+ return {
31725
+ success: true,
31726
+ error: null
31727
+ };
31728
+ }
31729
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
31730
+ return new CortexcodeMcp({
31731
+ outputRoot,
31732
+ relativeDirPath,
31733
+ relativeFilePath,
31734
+ fileContent: "{}",
31735
+ validate: false,
31736
+ global
31737
+ });
31738
+ }
31739
+ };
31740
+ //#endregion
30046
31741
  //#region src/features/mcp/mcp-env-var-format.ts
30047
31742
  /**
30048
31743
  * Canonical rulesync env var reference pattern: `${VAR}` (but not `${env:VAR}`,
@@ -34004,6 +35699,211 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
34004
35699
  }
34005
35700
  };
34006
35701
  //#endregion
35702
+ //#region src/features/mcp/tabnine-mcp.ts
35703
+ /**
35704
+ * The remote `type` Tabnine CLI reads a server as. Tabnine documents `"http"`
35705
+ * (streamable HTTP, the default for a bare `url`) and `"sse"`; `streamable-http`
35706
+ * is the canonical alias for the former. A `ws(s)://` URL or any other stated
35707
+ * transport is something Tabnine cannot reach, so `undefined` tells the caller
35708
+ * to skip the server. `null` means "omit `type`": Tabnine then tries
35709
+ * streamable HTTP first and falls back to SSE on its own.
35710
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-agent/mcp-intro-and-setup/mcp-server-config
35711
+ */
35712
+ function asTabnineRemoteType(stated, url) {
35713
+ if (stated === "sse") return "sse";
35714
+ if (stated === "http" || stated === "streamable-http") return "http";
35715
+ if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : null;
35716
+ }
35717
+ /**
35718
+ * Convert the canonical server map to the shape Tabnine CLI documents for the
35719
+ * `mcpServers` block of `settings.json`: a stdio server carries `command` (plus
35720
+ * `args`), a remote one carries `url` and an optional `type` (`"http"` or
35721
+ * `"sse"`). The canonical `transport` alias and the Claude-style `httpUrl`
35722
+ * alias are folded into `type`/`url`; `enabledTools`/`disabledTools` become
35723
+ * Tabnine's `includeTools`/`excludeTools`; `env`, `cwd`, `headers`, `timeout`
35724
+ * and `trust` pass through as documented.
35725
+ *
35726
+ * A server Tabnine cannot start or reach — no transport at all, a remote
35727
+ * transport without a URL, a WebSocket URL, or a stdio entry without a command
35728
+ * — is skipped with a warning rather than written in a broken form.
35729
+ */
35730
+ function convertToTabnineFormat(mcpServers, logger) {
35731
+ const result = {};
35732
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
35733
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
35734
+ if (declaresNoTransport(serverConfig)) {
35735
+ warnAndSkipMcpServer({
35736
+ toolName: "Tabnine CLI",
35737
+ serverName,
35738
+ reason: "no transport",
35739
+ logger
35740
+ });
35741
+ continue;
35742
+ }
35743
+ const { type, transport, url: _url, httpUrl: _httpUrl, command: _command, args: _args, enabledTools, disabledTools, ...rest } = serverConfig;
35744
+ const converted = {};
35745
+ if (isRemoteMcpServer(serverConfig)) {
35746
+ const url = resolveRemoteMcpUrl(serverConfig);
35747
+ if (!url) {
35748
+ warnAndSkipMcpServer({
35749
+ toolName: "Tabnine CLI",
35750
+ serverName,
35751
+ reason: "a remote transport without a url",
35752
+ logger
35753
+ });
35754
+ continue;
35755
+ }
35756
+ const stated = type ?? transport;
35757
+ const remoteType = asTabnineRemoteType(stated, url);
35758
+ if (remoteType === void 0) {
35759
+ warnAndSkipMcpServer({
35760
+ toolName: "Tabnine CLI",
35761
+ serverName,
35762
+ 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)`,
35763
+ logger
35764
+ });
35765
+ continue;
35766
+ }
35767
+ converted.url = url;
35768
+ if (remoteType !== null) converted.type = remoteType;
35769
+ } else {
35770
+ const [command, ...args] = resolveLocalMcpCommand(serverConfig);
35771
+ if (!command) {
35772
+ warnAndSkipMcpServer({
35773
+ toolName: "Tabnine CLI",
35774
+ serverName,
35775
+ reason: "a stdio transport without a command",
35776
+ logger
35777
+ });
35778
+ continue;
35779
+ }
35780
+ converted.command = command;
35781
+ if (args.length > 0) converted.args = args;
35782
+ }
35783
+ for (const [key, value] of Object.entries(rest)) {
35784
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
35785
+ converted[key] = (key === "env" || key === "headers") && isRecord$1(value) ? omitPrototypePollutionKeys(value) : value;
35786
+ }
35787
+ if (enabledTools !== void 0) converted.includeTools = enabledTools;
35788
+ if (disabledTools !== void 0) converted.excludeTools = disabledTools;
35789
+ result[serverName] = converted;
35790
+ }
35791
+ return result;
35792
+ }
35793
+ /**
35794
+ * Convert Tabnine's server map back to the canonical shape: `includeTools` /
35795
+ * `excludeTools` become `enabledTools` / `disabledTools`; `type` (`stdio`,
35796
+ * `sse`, `http`) and every other documented field are already canonical.
35797
+ */
35798
+ function convertFromTabnineFormat(mcpServers) {
35799
+ if (!isMcpServers(mcpServers)) return {};
35800
+ const result = {};
35801
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
35802
+ if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
35803
+ const converted = {};
35804
+ for (const [key, value] of Object.entries(serverConfig)) {
35805
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
35806
+ if (key === "includeTools") converted.enabledTools = value;
35807
+ else if (key === "excludeTools") converted.disabledTools = value;
35808
+ else converted[key] = value;
35809
+ }
35810
+ result[serverName] = converted;
35811
+ }
35812
+ return result;
35813
+ }
35814
+ /**
35815
+ * Tabnine CLI MCP configuration.
35816
+ *
35817
+ * Tabnine CLI reads `mcpServers` from `<project>/.tabnine/agent/settings.json`
35818
+ * (project scope) and `~/.tabnine/agent/settings.json` (user scope); the
35819
+ * project entry wins when both define the same server name. Both files hold
35820
+ * the user's other settings (`general`, `tools`, `hooks`, ...), so writes go
35821
+ * through the shared-config gateway with `mcpServers` as the only owned key
35822
+ * and the file is never deleted.
35823
+ *
35824
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-agent/mcp-intro-and-setup/mcp-server-config
35825
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings
35826
+ */
35827
+ var TabnineMcp = class TabnineMcp extends ToolMcp {
35828
+ json;
35829
+ constructor(params) {
35830
+ super(params);
35831
+ this.json = parseSharedConfig({
35832
+ format: "json",
35833
+ fileContent: this.fileContent || "{}",
35834
+ filePath: (0, node_path.join)(this.relativeDirPath, this.relativeFilePath),
35835
+ invalidRootPolicy: "error"
35836
+ });
35837
+ }
35838
+ getJson() {
35839
+ return this.json;
35840
+ }
35841
+ /**
35842
+ * settings.json carries the user's other Tabnine settings, so it must not
35843
+ * be deleted.
35844
+ */
35845
+ isDeletable() {
35846
+ return false;
35847
+ }
35848
+ static getSettablePaths(_options = {}) {
35849
+ return {
35850
+ relativeDirPath: TABNINE_AGENT_DIR_PATH,
35851
+ relativeFilePath: TABNINE_SETTINGS_FILE_NAME
35852
+ };
35853
+ }
35854
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
35855
+ const paths = this.getSettablePaths({ global });
35856
+ const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
35857
+ return new TabnineMcp({
35858
+ outputRoot,
35859
+ relativeDirPath: paths.relativeDirPath,
35860
+ relativeFilePath: paths.relativeFilePath,
35861
+ fileContent,
35862
+ validate,
35863
+ global
35864
+ });
35865
+ }
35866
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
35867
+ const paths = this.getSettablePaths({ global });
35868
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
35869
+ const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({ mcpServers: {} }, null, 2);
35870
+ return new TabnineMcp({
35871
+ outputRoot,
35872
+ relativeDirPath: paths.relativeDirPath,
35873
+ relativeFilePath: paths.relativeFilePath,
35874
+ fileContent: applySharedConfigPatch({
35875
+ fileKey: sharedConfigFileKey(paths),
35876
+ feature: "mcp",
35877
+ existingContent,
35878
+ patch: { mcpServers: convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger) },
35879
+ filePath
35880
+ }),
35881
+ validate,
35882
+ global
35883
+ });
35884
+ }
35885
+ toRulesyncMcp() {
35886
+ const mcpServers = convertFromTabnineFormat(this.json.mcpServers);
35887
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
35888
+ }
35889
+ validate() {
35890
+ return {
35891
+ success: true,
35892
+ error: null
35893
+ };
35894
+ }
35895
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
35896
+ return new TabnineMcp({
35897
+ outputRoot,
35898
+ relativeDirPath,
35899
+ relativeFilePath,
35900
+ fileContent: "{}",
35901
+ validate: false,
35902
+ global
35903
+ });
35904
+ }
35905
+ };
35906
+ //#endregion
34007
35907
  //#region src/features/mcp/takt-mcp.ts
34008
35908
  /**
34009
35909
  * MCP adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
@@ -34963,6 +36863,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
34963
36863
  supportsDisabledTools: false
34964
36864
  }
34965
36865
  }],
36866
+ ["bob", {
36867
+ class: BobMcp,
36868
+ meta: {
36869
+ supportsProject: true,
36870
+ supportsGlobal: true,
36871
+ supportsEnabledTools: false,
36872
+ supportsDisabledTools: false
36873
+ }
36874
+ }],
34966
36875
  ["claudecode", {
34967
36876
  class: ClaudecodeMcp,
34968
36877
  meta: {
@@ -35026,6 +36935,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
35026
36935
  supportsDisabledTools: false
35027
36936
  }
35028
36937
  }],
36938
+ ["cortexcode", {
36939
+ class: CortexcodeMcp,
36940
+ meta: {
36941
+ supportsProject: false,
36942
+ supportsGlobal: true,
36943
+ supportsEnabledTools: false,
36944
+ supportsDisabledTools: false
36945
+ }
36946
+ }],
35029
36947
  ["cursor", {
35030
36948
  class: CursorMcp,
35031
36949
  meta: {
@@ -35197,6 +37115,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
35197
37115
  supportsDisabledTools: false
35198
37116
  }
35199
37117
  }],
37118
+ ["tabnine", {
37119
+ class: TabnineMcp,
37120
+ meta: {
37121
+ supportsProject: true,
37122
+ supportsGlobal: true,
37123
+ supportsEnabledTools: true,
37124
+ supportsDisabledTools: true
37125
+ }
37126
+ }],
35200
37127
  ["takt", {
35201
37128
  class: TaktMcp,
35202
37129
  meta: {
@@ -36401,7 +38328,7 @@ const ANTIGRAVITY_CLI_OVERRIDE_KEYS = [
36401
38328
  "allowNonWorkspaceAccess",
36402
38329
  "agentMode"
36403
38330
  ];
36404
- const moduleLogger$3 = fallbackLogger;
38331
+ const moduleLogger$4 = fallbackLogger;
36405
38332
  /**
36406
38333
  * Narrow an imported `settings.json` value to one of the documented values of
36407
38334
  * an enum-typed override key. The override schema declares `toolPermission`,
@@ -36415,7 +38342,7 @@ const moduleLogger$3 = fallbackLogger;
36415
38342
  function pickDocumentedValue({ key, value, documented }) {
36416
38343
  if (typeof value !== "string") return void 0;
36417
38344
  if (documented.includes(value)) return value;
36418
- 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.`);
38345
+ 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.`);
36419
38346
  }
36420
38347
  /**
36421
38348
  * Deliberate mapping from rulesync canonical permission categories to the
@@ -36887,7 +38814,7 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
36887
38814
  }
36888
38815
  //#endregion
36889
38816
  //#region src/features/permissions/augmentcode-permissions.ts
36890
- const moduleLogger$2 = fallbackLogger;
38817
+ const moduleLogger$3 = fallbackLogger;
36891
38818
  zod_mini.z.enum([
36892
38819
  "allow",
36893
38820
  "deny",
@@ -37186,7 +39113,7 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
37186
39113
  const specialEntries = allEntries.filter((entry) => isSpecialEntry(entry));
37187
39114
  const result = { ...convertAugmentToRulesyncPermissions({
37188
39115
  entries: allEntries.filter((entry) => !isSpecialEntry(entry)),
37189
- logger: moduleLogger$2
39116
+ logger: moduleLogger$3
37190
39117
  }) };
37191
39118
  if (specialEntries.length > 0) result.augmentcode = { toolPermissions: specialEntries };
37192
39119
  return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
@@ -37560,6 +39487,14 @@ function collectRestrictionLosingSandboxEntries({ existing, merged, paths, toolL
37560
39487
  return entries;
37561
39488
  }
37562
39489
  /**
39490
+ * The `'<label>' — <reason>` list a trust-affecting warning ends with, shared so
39491
+ * an adapter that phrases the rest of the sentence itself (an import that lifts
39492
+ * the settings rather than writes them) prints the entries the same way.
39493
+ */
39494
+ function formatTrustAffectingEntries(entries) {
39495
+ return entries.map(({ label, reason }) => `'${label}' — ${reason}`).join("; ");
39496
+ }
39497
+ /**
37563
39498
  * The one warning that names every trust-affecting setting this generate wrote
37564
39499
  * to `relativeFilePath`. Emitted once per file: the individual reasons are what
37565
39500
  * matter, but the "review this as you would a hook" framing only needs saying
@@ -37570,7 +39505,7 @@ function collectRestrictionLosingSandboxEntries({ existing, merged, paths, toolL
37570
39505
  function warnOnTrustAffectingEntries({ toolLabel, noun = "setting", entries, relativeFilePath, logger }) {
37571
39506
  if (entries.length === 0) return;
37572
39507
  const one = entries.length === 1;
37573
- const details = entries.map(({ label, reason }) => `'${label}' — ${reason}`).join("; ");
39508
+ const details = formatTrustAffectingEntries(entries);
37574
39509
  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}.`);
37575
39510
  }
37576
39511
  //#endregion
@@ -43692,7 +45627,7 @@ const QwenSettingsPermissionsSchema = zod_mini.z.looseObject({
43692
45627
  deny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
43693
45628
  });
43694
45629
  const QwenSettingsSchema = zod_mini.z.looseObject({ permissions: zod_mini.z.optional(QwenSettingsPermissionsSchema) });
43695
- const moduleLogger$1 = fallbackLogger;
45630
+ const moduleLogger$2 = fallbackLogger;
43696
45631
  /**
43697
45632
  * Mapping from rulesync canonical tool category names (lowercase) to Qwen Code tool names (PascalCase).
43698
45633
  * Unknown names pass through as-is (e.g., mcp__server__tool).
@@ -44055,7 +45990,7 @@ function warnAboutImportedScopedKeys(groups) {
44055
45990
  if (!announceOnImport) continue;
44056
45991
  if (announceOnlyGrants && !grantsSomething(value, scopedKey)) continue;
44057
45992
  const globalNote = scopedKey.globalNote ?? QWEN_SCOPE_RULES[rule].globalNote;
44058
- moduleLogger$1.warn(`Qwen permissions: imported ${quoteQualifiedKey({
45993
+ moduleLogger$2.warn(`Qwen permissions: imported ${quoteQualifiedKey({
44059
45994
  groupName,
44060
45995
  key
44061
45996
  })} = ${quoteValueForWarning(value)}. ${globalNote} Review it before generating with the global scope.`);
@@ -44248,7 +46183,7 @@ function convertRulesyncToQwenPermissions(config) {
44248
46183
  }
44249
46184
  function convertQwenToRulesyncPermissions(params) {
44250
46185
  const permission = {};
44251
- const logger = params.logger ?? moduleLogger$1;
46186
+ const logger = params.logger ?? moduleLogger$2;
44252
46187
  const processEntries = (entries, action) => {
44253
46188
  for (const entry of entries) {
44254
46189
  const parsed = parseQwenPermissionEntry(entry, { logger });
@@ -45574,6 +47509,585 @@ function isPermissionAction(value) {
45574
47509
  return PermissionActionSchema.safeParse(value).success;
45575
47510
  }
45576
47511
  //#endregion
47512
+ //#region src/features/permissions/tabnine-permissions.ts
47513
+ /** The `settings.json` key group that carries the two tool lists. */
47514
+ const TOOLS_KEY = "tools";
47515
+ /** `tools.allowed`: tool names (and `run_shell_command(<prefix>)`) that skip the confirmation prompt. */
47516
+ const ALLOWED_KEY = "allowed";
47517
+ /** `tools.exclude`: tool names removed from discovery entirely. */
47518
+ const EXCLUDE_KEY = "exclude";
47519
+ /** The `settings.json` key group whose keys the `tabnine` override may author. */
47520
+ const GENERAL_KEY = "general";
47521
+ /** The Tabnine CLI built-in that runs shell commands. */
47522
+ const SHELL_TOOL_NAME = "run_shell_command";
47523
+ /**
47524
+ * The paths of the `tabnine` override that are refused with a warning rather
47525
+ * than written — the line Claude Code's sandbox paths draw. Under `tools`, the
47526
+ * values Tabnine CLI spawns as a command (`discoveryCommand` and `callCommand`
47527
+ * at startup, `shell.pager` on every shell output, `sandbox.command` to start
47528
+ * the sandbox); under `general`, the host every login and API call goes to
47529
+ * (`tabnineHost`) and the editor executable (`preferredEditor`, "any editor
47530
+ * path"). A fetched `.rulesync/permissions.jsonc` must not be able to point
47531
+ * Tabnine CLI at an executable, or a server, of its choosing.
47532
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference
47533
+ */
47534
+ const REFUSED_OVERRIDE_PATHS = {
47535
+ [TOOLS_KEY]: [
47536
+ ["discoveryCommand"],
47537
+ ["callCommand"],
47538
+ ["shell", "pager"],
47539
+ ["sandbox", "command"]
47540
+ ],
47541
+ [GENERAL_KEY]: [["tabnineHost"], ["preferredEditor"]]
47542
+ };
47543
+ /** `tools.sandbox`: `true` keeps tool calls contained; anything else disables or picks the sandbox. */
47544
+ const SANDBOX_KEY = "sandbox";
47545
+ /** `general.defaultApprovalMode`: the modes that still ask before a tool call runs. */
47546
+ const DEFAULT_APPROVAL_MODE_KEY = "defaultApprovalMode";
47547
+ const PROMPTING_APPROVAL_MODES = /* @__PURE__ */ new Set(["default", "plan"]);
47548
+ /**
47549
+ * Canonical categories with a one-to-one Tabnine CLI built-in tool. `bash` is
47550
+ * handled separately because it is the only tool whose list entries carry a
47551
+ * command prefix.
47552
+ *
47553
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/built-in-tools
47554
+ */
47555
+ const CANONICAL_TO_TABNINE_TOOL_NAMES = {
47556
+ read: "read_file",
47557
+ edit: "replace",
47558
+ write: "write_file",
47559
+ webfetch: "web_fetch",
47560
+ websearch: "google_web_search",
47561
+ grep: "grep_search",
47562
+ glob: "glob"
47563
+ };
47564
+ const TABNINE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_TABNINE_TOOL_NAMES).map(([canonical, tabnine]) => [tabnine, canonical]));
47565
+ const moduleLogger$1 = fallbackLogger;
47566
+ /**
47567
+ * Turn a canonical `bash` glob into the command prefix a
47568
+ * `run_shell_command(<prefix>)` entry carries. Tabnine matches the prefix
47569
+ * against the start of the command line, so only two glob shapes map cleanly:
47570
+ * `<prefix> *` and a bare `<prefix>` with no wildcard at all. Anything else
47571
+ * (`git * push`, `*.sh`, `npm run:*`) matches commands a prefix cannot name, so
47572
+ * it is reported and left out rather than approximated. `*` alone names the
47573
+ * tool itself and is returned as the empty prefix.
47574
+ */
47575
+ function toShellPrefix(pattern) {
47576
+ if (pattern === "*") return "";
47577
+ const prefix = pattern.endsWith(" *") ? pattern.slice(0, -2) : pattern;
47578
+ if (prefix.length === 0 || /[*?[\]]/.test(prefix)) return;
47579
+ return prefix;
47580
+ }
47581
+ /**
47582
+ * The Tabnine tool a canonical category names. An unknown category is passed
47583
+ * through as a tool name (that is how an MCP tool is named); the read is an
47584
+ * own-property lookup so a category spelled `toString` does not pick up an
47585
+ * `Object.prototype` member.
47586
+ */
47587
+ function toTabnineToolName(category) {
47588
+ return lookupOwn({
47589
+ record: CANONICAL_TO_TABNINE_TOOL_NAMES,
47590
+ key: category
47591
+ }) ?? category;
47592
+ }
47593
+ /**
47594
+ * The widest glob a `bash` pattern stands for once it is written as a
47595
+ * `run_shell_command(<prefix>)` entry: the prefix matches the start of the
47596
+ * command line, so a bare `pnpm` also covers `pnpm install`. Used only to
47597
+ * compare allows against the restrictions (`bash` deny and `ask` rules, and
47598
+ * the rules of `*`), where widening can only withhold more, never fail open.
47599
+ */
47600
+ function widenToPrefixGlob(pattern) {
47601
+ const prefix = toShellPrefix(pattern);
47602
+ if (prefix === void 0) return pattern;
47603
+ return prefix === "" ? "*" : `${prefix}*`;
47604
+ }
47605
+ function toShellEntry(prefix) {
47606
+ return prefix === "" ? SHELL_TOOL_NAME : `${SHELL_TOOL_NAME}(${prefix})`;
47607
+ }
47608
+ /** The `bash` pattern a `run_shell_command(<prefix>)` entry stands for. */
47609
+ function toShellPattern(prefix) {
47610
+ return prefix === void 0 || prefix === "" ? "*" : `${prefix} *`;
47611
+ }
47612
+ /**
47613
+ * Split a `tools.allowed`/`tools.exclude` entry into its tool name and the
47614
+ * optional `(<prefix>)` suffix. An entry whose parenthesis never closes, or
47615
+ * that carries text after the closing one, is malformed and yields `undefined`.
47616
+ */
47617
+ function parseTabnineEntry(entry) {
47618
+ const open = entry.indexOf("(");
47619
+ if (open === -1) return entry.length > 0 ? {
47620
+ toolName: entry,
47621
+ prefix: void 0
47622
+ } : void 0;
47623
+ if (open === 0 || !entry.endsWith(")")) return;
47624
+ return {
47625
+ toolName: entry.slice(0, open),
47626
+ prefix: entry.slice(open + 1, -1)
47627
+ };
47628
+ }
47629
+ /**
47630
+ * `record` without the value at `path`, when there is one, and without any
47631
+ * container the removal emptied. Containers on the way are copied, never
47632
+ * mutated, so the override the caller read stays intact.
47633
+ */
47634
+ function omitPath({ record, path }) {
47635
+ const [head, ...rest] = path;
47636
+ if (head === void 0 || !Object.hasOwn(record, head)) return {
47637
+ record,
47638
+ removed: false
47639
+ };
47640
+ if (rest.length === 0) {
47641
+ const { [head]: _removed, ...others } = record;
47642
+ return {
47643
+ record: others,
47644
+ removed: true
47645
+ };
47646
+ }
47647
+ const child = record[head];
47648
+ if (!isRecord$1(child)) return {
47649
+ record,
47650
+ removed: false
47651
+ };
47652
+ const inner = omitPath({
47653
+ record: child,
47654
+ path: rest
47655
+ });
47656
+ if (!inner.removed) return {
47657
+ record,
47658
+ removed: false
47659
+ };
47660
+ if (Object.keys(inner.record).length === 0) {
47661
+ const { [head]: _emptied, ...others } = record;
47662
+ return {
47663
+ record: others,
47664
+ removed: true
47665
+ };
47666
+ }
47667
+ return {
47668
+ record: {
47669
+ ...record,
47670
+ [head]: inner.record
47671
+ },
47672
+ removed: true
47673
+ };
47674
+ }
47675
+ /**
47676
+ * The `tabnine.<group>` record with every refused path removed, and the dotted
47677
+ * names of the paths that were there. Nothing else of the group is touched:
47678
+ * the rest is written verbatim (and announced where it widens trust).
47679
+ */
47680
+ function stripRefusedPaths({ group, record }) {
47681
+ const refused = [];
47682
+ let stripped = record;
47683
+ for (const path of REFUSED_OVERRIDE_PATHS[group]) {
47684
+ const result = omitPath({
47685
+ record: stripped,
47686
+ path
47687
+ });
47688
+ if (result.removed) {
47689
+ refused.push(`${group}.${path.join(".")}`);
47690
+ stripped = result.record;
47691
+ }
47692
+ }
47693
+ return {
47694
+ record: stripped,
47695
+ refused
47696
+ };
47697
+ }
47698
+ /**
47699
+ * The `tools` and `general` groups of an override with the refused paths taken
47700
+ * out, and every refused path named. A `general` that is absent or that the
47701
+ * refusals emptied is reported as absent so no `general: {}` is written or
47702
+ * lifted. (The `TabninePermissionsOverrideSchema` already rejects a group that
47703
+ * is not an object; the `isRecord` guards only keep an unvalidated file from
47704
+ * throwing.)
47705
+ */
47706
+ function stripRefusedOverridePaths(override) {
47707
+ const tools = stripRefusedPaths({
47708
+ group: TOOLS_KEY,
47709
+ record: isRecord$1(override[TOOLS_KEY]) ? override[TOOLS_KEY] : {}
47710
+ });
47711
+ const general = isRecord$1(override[GENERAL_KEY]) ? stripRefusedPaths({
47712
+ group: GENERAL_KEY,
47713
+ record: override[GENERAL_KEY]
47714
+ }) : void 0;
47715
+ return {
47716
+ tools: tools.record,
47717
+ general: general !== void 0 && Object.keys(general.record).length > 0 ? general.record : void 0,
47718
+ refused: [...tools.refused, ...general?.refused ?? []]
47719
+ };
47720
+ }
47721
+ /**
47722
+ * The settings of a `tabnine` override that widen what Tabnine CLI does without
47723
+ * asking, so the generate can name them. `tools.allowed` counts because an
47724
+ * override-authored entry skips the confirmation prompt on the strength of the
47725
+ * override alone (its `run_shell_command(...)` entries are not here: they are
47726
+ * compared as `bash` allows and withheld like them); `tools.sandbox` and
47727
+ * `general.defaultApprovalMode` decide whether tool calls are contained and
47728
+ * whether they are confirmed at all. Called on the override, not the file, so a
47729
+ * value the file already held is not reported as something rulesync opened.
47730
+ */
47731
+ function collectTrustAffectingOverrideEntries({ tools, general }) {
47732
+ const entries = [];
47733
+ const allowed = isStringArray$2(tools[ALLOWED_KEY]) ? tools[ALLOWED_KEY] : [];
47734
+ if (allowed.length > 0) entries.push({
47735
+ label: `tools.allowed (${allowed.map(quoteValueForWarning).join(", ")})`,
47736
+ reason: "auto-approves what it names as the override spells it; a non-shell entry has no canonical rule to be checked against"
47737
+ });
47738
+ if (Object.hasOwn(tools, SANDBOX_KEY) && isNotTrue(tools[SANDBOX_KEY])) entries.push({
47739
+ label: `tools.sandbox = ${quoteValueForWarning(tools[SANDBOX_KEY])}`,
47740
+ 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"
47741
+ });
47742
+ const approvalMode = general?.[DEFAULT_APPROVAL_MODE_KEY];
47743
+ if (general !== void 0 && Object.hasOwn(general, DEFAULT_APPROVAL_MODE_KEY) && !PROMPTING_APPROVAL_MODES.has(approvalMode)) entries.push({
47744
+ label: `general.defaultApprovalMode = ${quoteValueForWarning(approvalMode)}`,
47745
+ reason: "auto-approves tool calls instead of asking; only `default` and `plan` keep the prompt"
47746
+ });
47747
+ return entries;
47748
+ }
47749
+ /**
47750
+ * What a later generate would announce from the override an import builds,
47751
+ * said now, so the file the import writes into `.rulesync/` is read with the
47752
+ * same eyes as one that arrived with a clone. `refused` names the paths the
47753
+ * import left in `settings.json`, the ones generate refuses to write — an
47754
+ * override must not carry a value that only ever produces a warning.
47755
+ */
47756
+ function announceLiftedOverride({ tools, general, refused }) {
47757
+ if (refused.length > 0) {
47758
+ const one = refused.length === 1;
47759
+ 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.`);
47760
+ }
47761
+ const trustAffecting = collectTrustAffectingOverrideEntries({
47762
+ tools,
47763
+ general
47764
+ });
47765
+ if (trustAffecting.length > 0) {
47766
+ const one = trustAffecting.length === 1;
47767
+ 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)}.`);
47768
+ }
47769
+ }
47770
+ /**
47771
+ * Build the two tool lists from the canonical block; the rules that could not
47772
+ * be carried are reported through `logger` here.
47773
+ */
47774
+ function buildToolLists({ permission, overrideShellAllowPatterns, logger }) {
47775
+ const allowed = [];
47776
+ const exclude = [];
47777
+ const { rules: canonicalRules, ignoredAllToolsAllowPatterns } = collectShellCommandRules(permission);
47778
+ const { allow: shellAllow, deny: shellDeny, shadowedAllowPatterns, unwrittenDenyPatterns, unenforcedAllToolsAskPatterns, intersectionBudgetExhausted } = partitionCommandRules({
47779
+ rules: [...canonicalRules, ...overrideShellAllowPatterns.map((pattern) => ({
47780
+ pattern,
47781
+ action: "allow",
47782
+ fromAllToolsCategory: false
47783
+ }))],
47784
+ writesAllToolsDeny: false,
47785
+ normalizePattern: widenToPrefixGlob
47786
+ });
47787
+ warnAboutUnwrittenCommandRules({
47788
+ toolLabel: "Tabnine CLI",
47789
+ surfaceLabel: "tools.allowed/tools.exclude",
47790
+ foreignRestrictingCategories: [],
47791
+ shadowedAllowPatterns,
47792
+ unwrittenDenyPatterns,
47793
+ unwrittenDenyReason: "tools.exclude removes a tool from discovery, and a pattern written under '*' need not be a command at all.",
47794
+ unenforcedAllToolsAskPatterns,
47795
+ ignoredAllToolsAllowPatterns,
47796
+ intersectionBudgetExhausted,
47797
+ logger
47798
+ });
47799
+ const unmappedShellPatterns = [];
47800
+ for (const pattern of shellDeny) {
47801
+ const prefix = toShellPrefix(pattern);
47802
+ if (prefix === void 0) {
47803
+ unmappedShellPatterns.push(pattern);
47804
+ continue;
47805
+ }
47806
+ exclude.push(toShellEntry(prefix));
47807
+ }
47808
+ const shadowingShellDenies = createShadowingRestrictionsTest(shellDeny.map((pattern) => ({
47809
+ pattern,
47810
+ fromAllToolsCategory: false
47811
+ })), { normalizePattern: widenToPrefixGlob });
47812
+ const withheldAllowPatterns = [];
47813
+ for (const pattern of shellAllow) {
47814
+ const prefix = toShellPrefix(pattern);
47815
+ if (prefix === void 0) {
47816
+ unmappedShellPatterns.push(pattern);
47817
+ continue;
47818
+ }
47819
+ if (shadowingShellDenies(pattern).length > 0) {
47820
+ withheldAllowPatterns.push(pattern);
47821
+ continue;
47822
+ }
47823
+ allowed.push(toShellEntry(prefix));
47824
+ }
47825
+ 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.`);
47826
+ 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.`);
47827
+ const unmappedPatterns = [];
47828
+ for (const [category, categoryRules] of Object.entries(permission)) {
47829
+ if (category === "bash" || category === "*") continue;
47830
+ const toolName = toTabnineToolName(category);
47831
+ for (const [pattern, action] of Object.entries(categoryRules)) {
47832
+ if (pattern !== "*") {
47833
+ unmappedPatterns.push(`${category}: ${pattern}`);
47834
+ continue;
47835
+ }
47836
+ if (action === "allow") allowed.push(toolName);
47837
+ else if (action === "deny") exclude.push(toolName);
47838
+ }
47839
+ }
47840
+ 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.`);
47841
+ return {
47842
+ allowed: (0, es_toolkit.uniq)(allowed),
47843
+ exclude: (0, es_toolkit.uniq)(exclude)
47844
+ };
47845
+ }
47846
+ /**
47847
+ * Permissions generator for the Tabnine CLI.
47848
+ *
47849
+ * Tabnine CLI keeps its tool policy in `.tabnine/agent/settings.json` (project)
47850
+ * and `~/.tabnine/agent/settings.json` (user), a shared file that also carries
47851
+ * `mcpServers`, `hooks`, `general.*`, `context.*` and more, so writes go through
47852
+ * the shared-config gateway as a deep merge and the file is never deleted.
47853
+ *
47854
+ * Two keys are driven by the canonical block:
47855
+ * - `tools.allowed` — `allow` rules. A tool name skips the confirmation prompt;
47856
+ * `run_shell_command(<prefix>)` narrows that to one command prefix.
47857
+ * - `tools.exclude` — `deny` rules. A tool name removes the tool from discovery.
47858
+ * A `bash` deny is written as `run_shell_command(<prefix>)` there, the form
47859
+ * Gemini CLI's `excludeTools` (which Tabnine derives from) honors, but Tabnine
47860
+ * documents the prefix for `tools.allowed` only — so the entry is not relied
47861
+ * on, and every allow the deny overlaps is withheld as well.
47862
+ *
47863
+ * `ask` writes nothing: a tool that is in neither list keeps Tabnine's default
47864
+ * prompt. Every other `tools.*` key (`core`, `shell.*`, `enableWebTools`, ...)
47865
+ * and the `general.*` group are authored through the `tabnine` override, which
47866
+ * is deep-merged beneath the two canonical lists; entries in the lists that
47867
+ * cannot be mapped back (a prefix on a non-shell tool, an MCP tool name) round
47868
+ * trip through the override too.
47869
+ *
47870
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/built-in-tools
47871
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference
47872
+ */
47873
+ var TabninePermissions = class TabninePermissions extends ToolPermissions {
47874
+ constructor(params) {
47875
+ super({
47876
+ ...params,
47877
+ fileContent: params.fileContent ?? "{}"
47878
+ });
47879
+ }
47880
+ /**
47881
+ * `settings.json` holds unrelated user settings (`mcpServers`, `hooks`,
47882
+ * `general`, ...), so it must not be deleted.
47883
+ */
47884
+ isDeletable() {
47885
+ return false;
47886
+ }
47887
+ static getSettablePaths(_options = {}) {
47888
+ return {
47889
+ relativeDirPath: TABNINE_AGENT_DIR_PATH,
47890
+ relativeFilePath: TABNINE_SETTINGS_FILE_NAME
47891
+ };
47892
+ }
47893
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
47894
+ const paths = TabninePermissions.getSettablePaths({ global });
47895
+ const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
47896
+ return new TabninePermissions({
47897
+ outputRoot,
47898
+ relativeDirPath: paths.relativeDirPath,
47899
+ relativeFilePath: paths.relativeFilePath,
47900
+ fileContent,
47901
+ validate,
47902
+ global
47903
+ });
47904
+ }
47905
+ static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
47906
+ const paths = TabninePermissions.getSettablePaths({ global });
47907
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
47908
+ const existingContent = await readFileContentOrNull(filePath) ?? "{}";
47909
+ const config = rulesyncPermissions.getJson();
47910
+ const override = isRecord$1(config.tabnine) ? config.tabnine : {};
47911
+ const { tools: overrideTools, general: overrideGeneral, refused: refusedPaths } = stripRefusedOverridePaths(override);
47912
+ 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 ${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)} if you need it.`);
47913
+ const overrideList = (key) => {
47914
+ const value = overrideTools[key];
47915
+ if (value === void 0 || isStringArray$2(value)) return value ?? [];
47916
+ warnWithFallback(logger, `Tabnine CLI permissions: ignored tools.${key} of the tabnine override (${quoteValueForWarning(value)}); it must be a list of tool names.`);
47917
+ return [];
47918
+ };
47919
+ const overrideShellAllowPatterns = [];
47920
+ const overrideVerbatimAllowed = [];
47921
+ for (const entry of overrideList(ALLOWED_KEY)) {
47922
+ const parsed = parseTabnineEntry(entry);
47923
+ if (parsed?.toolName === SHELL_TOOL_NAME) overrideShellAllowPatterns.push(toShellPattern(parsed.prefix));
47924
+ else overrideVerbatimAllowed.push(entry);
47925
+ }
47926
+ const overrideExclude = overrideList(EXCLUDE_KEY);
47927
+ const { allowed, exclude } = buildToolLists({
47928
+ permission: config.permission,
47929
+ overrideShellAllowPatterns,
47930
+ logger
47931
+ });
47932
+ const ignoredOverrideKeys = Object.keys(override).filter((key) => key !== TOOLS_KEY && key !== GENERAL_KEY);
47933
+ 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.`);
47934
+ const existingTools = this.existingToolsGroup(existingContent);
47935
+ const managedToolNames = new Set(Object.keys(config.permission).map((category) => category === "bash" || category === "*" ? SHELL_TOOL_NAME : toTabnineToolName(category)));
47936
+ const preservedEntries = (key) => (isStringArray$2(existingTools?.[key]) ? existingTools[key] : []).filter((entry) => {
47937
+ const toolName = parseTabnineEntry(entry)?.toolName;
47938
+ return toolName === void 0 || !managedToolNames.has(toolName);
47939
+ });
47940
+ const allowedList = (0, es_toolkit.uniq)([
47941
+ ...allowed,
47942
+ ...overrideVerbatimAllowed,
47943
+ ...preservedEntries(ALLOWED_KEY)
47944
+ ]);
47945
+ const excludeList = (0, es_toolkit.uniq)([
47946
+ ...exclude,
47947
+ ...overrideExclude,
47948
+ ...preservedEntries(EXCLUDE_KEY)
47949
+ ]);
47950
+ const droppedExcludeEntries = (0, es_toolkit.uniq)(isStringArray$2(existingTools?.[EXCLUDE_KEY]) ? existingTools[EXCLUDE_KEY] : []).filter((entry) => {
47951
+ if (excludeList.includes(entry)) return false;
47952
+ const toolName = parseTabnineEntry(entry)?.toolName;
47953
+ return toolName === void 0 || !excludeList.includes(toolName);
47954
+ });
47955
+ 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.`);
47956
+ const tools = {
47957
+ ...overrideTools,
47958
+ [ALLOWED_KEY]: allowedList.length > 0 ? allowedList : void 0,
47959
+ [EXCLUDE_KEY]: excludeList.length > 0 ? excludeList : void 0
47960
+ };
47961
+ const patch = {};
47962
+ if (overrideGeneral !== void 0) patch[GENERAL_KEY] = overrideGeneral;
47963
+ if (Object.values(tools).some((value) => value !== void 0) || existingTools !== void 0) patch[TOOLS_KEY] = tools;
47964
+ warnOnTrustAffectingEntries({
47965
+ toolLabel: "Tabnine CLI",
47966
+ entries: collectTrustAffectingOverrideEntries({
47967
+ tools: {
47968
+ ...overrideTools,
47969
+ [ALLOWED_KEY]: overrideVerbatimAllowed
47970
+ },
47971
+ general: overrideGeneral
47972
+ }),
47973
+ relativeFilePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath),
47974
+ logger
47975
+ });
47976
+ return new TabninePermissions({
47977
+ outputRoot,
47978
+ relativeDirPath: paths.relativeDirPath,
47979
+ relativeFilePath: paths.relativeFilePath,
47980
+ fileContent: applySharedConfigPatch({
47981
+ fileKey: sharedConfigFileKey(paths),
47982
+ feature: "permissions",
47983
+ existingContent,
47984
+ patch,
47985
+ filePath
47986
+ }),
47987
+ validate: true,
47988
+ global
47989
+ });
47990
+ }
47991
+ /** The `tools` group of the existing file, or `undefined` when it has none. */
47992
+ static existingToolsGroup(existingContent) {
47993
+ try {
47994
+ const existing = parseSharedConfig({
47995
+ format: "json",
47996
+ fileContent: existingContent || "{}",
47997
+ invalidRootPolicy: "error"
47998
+ });
47999
+ return isRecord$1(existing[TOOLS_KEY]) ? existing[TOOLS_KEY] : void 0;
48000
+ } catch {
48001
+ return;
48002
+ }
48003
+ }
48004
+ toRulesyncPermissions() {
48005
+ const settings = this.parseSettings();
48006
+ const tools = isRecord$1(settings[TOOLS_KEY]) ? settings[TOOLS_KEY] : {};
48007
+ const permission = {};
48008
+ const leftovers = {};
48009
+ const setRule = (category, pattern, action) => {
48010
+ const rules = Object.hasOwn(permission, category) ? permission[category] : permission[category] = {};
48011
+ if (rules !== void 0 && !Object.hasOwn(rules, pattern)) rules[pattern] = action;
48012
+ };
48013
+ for (const [key, action] of [[EXCLUDE_KEY, "deny"], [ALLOWED_KEY, "allow"]]) {
48014
+ const entries = isStringArray$2(tools[key]) ? tools[key] : [];
48015
+ for (const entry of entries) {
48016
+ const parsed = parseTabnineEntry(entry);
48017
+ if (parsed === void 0) {
48018
+ moduleLogger$1.warn(`Tabnine CLI permissions: kept malformed tools.${key} entry ${quoteValueForWarning(entry)} in the tabnine override.`);
48019
+ (leftovers[key] ??= []).push(entry);
48020
+ continue;
48021
+ }
48022
+ const { toolName, prefix } = parsed;
48023
+ if (toolName === SHELL_TOOL_NAME) {
48024
+ setRule(SHELL_PERMISSION_CATEGORY, toShellPattern(prefix), action);
48025
+ continue;
48026
+ }
48027
+ if (prefix !== void 0) {
48028
+ (leftovers[key] ??= []).push(entry);
48029
+ continue;
48030
+ }
48031
+ const category = lookupOwn({
48032
+ record: TABNINE_TO_CANONICAL_TOOL_NAMES,
48033
+ key: toolName
48034
+ }) ?? toolName;
48035
+ if (isPrototypePollutionKey(category)) {
48036
+ moduleLogger$1.warn(`Tabnine CLI permissions: kept tools.${key} entry ${quoteValueForWarning(entry)} in the tabnine override; its name cannot be a permission category.`);
48037
+ (leftovers[key] ??= []).push(entry);
48038
+ continue;
48039
+ }
48040
+ setRule(category, "*", action);
48041
+ }
48042
+ }
48043
+ const { tools: overrideTools, general, refused } = stripRefusedOverridePaths({
48044
+ [TOOLS_KEY]: Object.fromEntries(Object.entries(tools).filter(([key]) => key !== ALLOWED_KEY && key !== EXCLUDE_KEY)),
48045
+ [GENERAL_KEY]: settings[GENERAL_KEY]
48046
+ });
48047
+ for (const [key, entries] of Object.entries(leftovers)) overrideTools[key] = entries;
48048
+ const override = {};
48049
+ if (Object.keys(overrideTools).length > 0) override[TOOLS_KEY] = overrideTools;
48050
+ if (general !== void 0) override[GENERAL_KEY] = general;
48051
+ announceLiftedOverride({
48052
+ tools: overrideTools,
48053
+ general,
48054
+ refused
48055
+ });
48056
+ const imported = { permission };
48057
+ if (Object.keys(override).length > 0) imported.tabnine = override;
48058
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(imported, null, 2) });
48059
+ }
48060
+ parseSettings() {
48061
+ const relativePath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
48062
+ try {
48063
+ return parseSharedConfig({
48064
+ format: "json",
48065
+ fileContent: this.getFileContent() || "{}",
48066
+ filePath: relativePath,
48067
+ invalidRootPolicy: "error"
48068
+ });
48069
+ } catch (error) {
48070
+ throw new Error(`Failed to parse Tabnine CLI settings in ${relativePath}: ${formatError(error)}`, { cause: error });
48071
+ }
48072
+ }
48073
+ validate() {
48074
+ return {
48075
+ success: true,
48076
+ error: null
48077
+ };
48078
+ }
48079
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
48080
+ return new TabninePermissions({
48081
+ outputRoot,
48082
+ relativeDirPath,
48083
+ relativeFilePath,
48084
+ fileContent: "{}",
48085
+ validate: false,
48086
+ global
48087
+ });
48088
+ }
48089
+ };
48090
+ //#endregion
45577
48091
  //#region src/features/permissions/takt-permissions.ts
45578
48092
  const TAKT_PROVIDER_KEY = "provider";
45579
48093
  const TAKT_PROVIDER_PROFILES_KEY = "provider_profiles";
@@ -48953,6 +51467,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
48953
51467
  supportsImport: true
48954
51468
  }
48955
51469
  }],
51470
+ ["tabnine", {
51471
+ class: TabninePermissions,
51472
+ meta: {
51473
+ supportsProject: true,
51474
+ supportsGlobal: true,
51475
+ supportsImport: true
51476
+ }
51477
+ }],
48956
51478
  ["takt", {
48957
51479
  class: TaktPermissions,
48958
51480
  meta: {
@@ -50250,16 +52772,198 @@ var AntigravitySharedSkill = class extends ToolSkill {
50250
52772
  }
50251
52773
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
50252
52774
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
50253
- const antigravityFrontmatter = {
52775
+ const antigravityFrontmatter = {
52776
+ name: rulesyncFrontmatter.name,
52777
+ description: rulesyncFrontmatter.description
52778
+ };
52779
+ const settablePaths = this.getSettablePaths({ global });
52780
+ return new this({
52781
+ outputRoot,
52782
+ relativeDirPath: settablePaths.relativeDirPath,
52783
+ dirName: rulesyncSkill.getDirName(),
52784
+ frontmatter: antigravityFrontmatter,
52785
+ body: rulesyncSkill.getBody(),
52786
+ otherFiles: rulesyncSkill.getOtherFiles(),
52787
+ validate,
52788
+ global
52789
+ });
52790
+ }
52791
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
52792
+ const targets = rulesyncSkill.getFrontmatter().targets;
52793
+ return targets.includes("*") || targets.includes(this.getToolTarget());
52794
+ }
52795
+ static async fromDir(params) {
52796
+ const loaded = await this.loadSkillDirContent({
52797
+ ...params,
52798
+ getSettablePaths: (options) => this.getSettablePaths(options)
52799
+ });
52800
+ const result = AntigravitySkillFrontmatterSchema.safeParse(loaded.frontmatter);
52801
+ if (!result.success) {
52802
+ const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
52803
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
52804
+ }
52805
+ return new this({
52806
+ outputRoot: loaded.outputRoot,
52807
+ relativeDirPath: loaded.relativeDirPath,
52808
+ dirName: loaded.dirName,
52809
+ frontmatter: result.data,
52810
+ body: loaded.body,
52811
+ otherFiles: loaded.otherFiles,
52812
+ validate: true,
52813
+ global: loaded.global
52814
+ });
52815
+ }
52816
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
52817
+ return new this({
52818
+ outputRoot,
52819
+ relativeDirPath,
52820
+ dirName,
52821
+ frontmatter: {
52822
+ name: "",
52823
+ description: ""
52824
+ },
52825
+ body: "",
52826
+ otherFiles: [],
52827
+ validate: false,
52828
+ global
52829
+ });
52830
+ }
52831
+ };
52832
+ //#endregion
52833
+ //#region src/features/skills/antigravity-cli-skill.ts
52834
+ /**
52835
+ * Represents a Google Antigravity CLI skill directory (Antigravity 2.0).
52836
+ *
52837
+ * Shares all behavior with {@link AntigravitySharedSkill}; the CLI keeps its
52838
+ * own global skills tree at `~/.gemini/antigravity-cli/skills/` and answers to
52839
+ * the `antigravity-cli` target.
52840
+ */
52841
+ var AntigravityCliSkill = class extends AntigravitySharedSkill {
52842
+ static getGlobalSubdir() {
52843
+ return ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR;
52844
+ }
52845
+ static getToolTarget() {
52846
+ return "antigravity-cli";
52847
+ }
52848
+ };
52849
+ //#endregion
52850
+ //#region src/features/skills/antigravity-ide-skill.ts
52851
+ /**
52852
+ * Represents a Google Antigravity IDE skill directory (Antigravity 2.0).
52853
+ *
52854
+ * Shares all behavior with {@link AntigravitySharedSkill}. As of Antigravity
52855
+ * 2.0 the IDE reads its global skills from the shared `~/.gemini/config/skills/`
52856
+ * location (the Skills doc lists `~/.gemini/config/skills/<skill-folder>/` as
52857
+ * the global scope); the legacy `~/.gemini/antigravity/skills/` path is no
52858
+ * longer used. It answers to the `antigravity-ide` target.
52859
+ */
52860
+ var AntigravityIdeSkill = class extends AntigravitySharedSkill {
52861
+ static getGlobalSubdir() {
52862
+ return ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR;
52863
+ }
52864
+ static getToolTarget() {
52865
+ return "antigravity-ide";
52866
+ }
52867
+ };
52868
+ //#endregion
52869
+ //#region src/features/skills/antigravity-plugin-skill.ts
52870
+ var AntigravityPluginSkill = class extends AntigravityIdeSkill {
52871
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
52872
+ const targets = rulesyncSkill.getFrontmatter().targets;
52873
+ return targets.includes("*") || targets.includes("antigravity-plugin");
52874
+ }
52875
+ static getSettablePaths() {
52876
+ return { relativeDirPath: ANTIGRAVITY_PLUGIN_SKILLS_DIR };
52877
+ }
52878
+ };
52879
+ //#endregion
52880
+ //#region src/features/skills/augmentcode-skill.ts
52881
+ const AugmentcodeSkillFrontmatterSchema = zod_mini.z.looseObject({
52882
+ name: zod_mini.z.string(),
52883
+ description: zod_mini.z.string()
52884
+ });
52885
+ /**
52886
+ * Represents an AugmentCode (Auggie CLI) skill directory.
52887
+ * Auggie has native skill support — it reads `<name>/SKILL.md` directories under
52888
+ * `.augment/skills/` (project) and `~/.augment/skills/` (global).
52889
+ * See https://docs.augmentcode.com/cli/skills
52890
+ */
52891
+ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
52892
+ constructor({ outputRoot = process.cwd(), relativeDirPath = AugmentcodeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
52893
+ super({
52894
+ outputRoot,
52895
+ relativeDirPath,
52896
+ dirName,
52897
+ mainFile: {
52898
+ name: SKILL_FILE_NAME,
52899
+ body,
52900
+ frontmatter: { ...frontmatter }
52901
+ },
52902
+ otherFiles,
52903
+ global
52904
+ });
52905
+ if (validate) {
52906
+ const result = this.validate();
52907
+ if (!result.success) throw result.error;
52908
+ }
52909
+ }
52910
+ static getSettablePaths({ global: _global = false } = {}) {
52911
+ return {
52912
+ relativeDirPath: AUGMENTCODE_SKILLS_DIR_PATH,
52913
+ alternativeSkillRoots: [AUGMENTCODE_AGENTS_SKILLS_DIR_PATH]
52914
+ };
52915
+ }
52916
+ getFrontmatter() {
52917
+ return AugmentcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
52918
+ }
52919
+ getBody() {
52920
+ return this.mainFile?.body ?? "";
52921
+ }
52922
+ validate() {
52923
+ if (this.mainFile === void 0) return {
52924
+ success: false,
52925
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
52926
+ };
52927
+ const result = AugmentcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
52928
+ if (!result.success) return {
52929
+ success: false,
52930
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
52931
+ };
52932
+ return {
52933
+ success: true,
52934
+ error: null
52935
+ };
52936
+ }
52937
+ toRulesyncSkill() {
52938
+ const frontmatter = this.getFrontmatter();
52939
+ const rulesyncFrontmatter = {
52940
+ name: frontmatter.name,
52941
+ description: frontmatter.description,
52942
+ targets: ["*"]
52943
+ };
52944
+ return new RulesyncSkill({
52945
+ outputRoot: this.outputRoot,
52946
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
52947
+ dirName: this.getDirName(),
52948
+ frontmatter: rulesyncFrontmatter,
52949
+ body: this.getBody(),
52950
+ otherFiles: this.getOtherFiles(),
52951
+ validate: true,
52952
+ global: this.global
52953
+ });
52954
+ }
52955
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
52956
+ const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
52957
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
52958
+ const augmentcodeFrontmatter = {
50254
52959
  name: rulesyncFrontmatter.name,
50255
52960
  description: rulesyncFrontmatter.description
50256
52961
  };
50257
- const settablePaths = this.getSettablePaths({ global });
50258
- return new this({
52962
+ return new AugmentcodeSkill({
50259
52963
  outputRoot,
50260
52964
  relativeDirPath: settablePaths.relativeDirPath,
50261
52965
  dirName: rulesyncSkill.getDirName(),
50262
- frontmatter: antigravityFrontmatter,
52966
+ frontmatter: augmentcodeFrontmatter,
50263
52967
  body: rulesyncSkill.getBody(),
50264
52968
  otherFiles: rulesyncSkill.getOtherFiles(),
50265
52969
  validate,
@@ -50268,19 +52972,19 @@ var AntigravitySharedSkill = class extends ToolSkill {
50268
52972
  }
50269
52973
  static isTargetedByRulesyncSkill(rulesyncSkill) {
50270
52974
  const targets = rulesyncSkill.getFrontmatter().targets;
50271
- return targets.includes("*") || targets.includes(this.getToolTarget());
52975
+ return targets.includes("*") || targets.includes("augmentcode");
50272
52976
  }
50273
52977
  static async fromDir(params) {
50274
52978
  const loaded = await this.loadSkillDirContent({
50275
52979
  ...params,
50276
- getSettablePaths: (options) => this.getSettablePaths(options)
52980
+ getSettablePaths: AugmentcodeSkill.getSettablePaths
50277
52981
  });
50278
- const result = AntigravitySkillFrontmatterSchema.safeParse(loaded.frontmatter);
52982
+ const result = AugmentcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
50279
52983
  if (!result.success) {
50280
52984
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
50281
52985
  throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
50282
52986
  }
50283
- return new this({
52987
+ return new AugmentcodeSkill({
50284
52988
  outputRoot: loaded.outputRoot,
50285
52989
  relativeDirPath: loaded.relativeDirPath,
50286
52990
  dirName: loaded.dirName,
@@ -50292,9 +52996,10 @@ var AntigravitySharedSkill = class extends ToolSkill {
50292
52996
  });
50293
52997
  }
50294
52998
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
50295
- return new this({
52999
+ const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
53000
+ return new AugmentcodeSkill({
50296
53001
  outputRoot,
50297
- relativeDirPath,
53002
+ relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
50298
53003
  dirName,
50299
53004
  frontmatter: {
50300
53005
  name: "",
@@ -50308,66 +53013,24 @@ var AntigravitySharedSkill = class extends ToolSkill {
50308
53013
  }
50309
53014
  };
50310
53015
  //#endregion
50311
- //#region src/features/skills/antigravity-cli-skill.ts
50312
- /**
50313
- * Represents a Google Antigravity CLI skill directory (Antigravity 2.0).
50314
- *
50315
- * Shares all behavior with {@link AntigravitySharedSkill}; the CLI keeps its
50316
- * own global skills tree at `~/.gemini/antigravity-cli/skills/` and answers to
50317
- * the `antigravity-cli` target.
50318
- */
50319
- var AntigravityCliSkill = class extends AntigravitySharedSkill {
50320
- static getGlobalSubdir() {
50321
- return ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR;
50322
- }
50323
- static getToolTarget() {
50324
- return "antigravity-cli";
50325
- }
50326
- };
50327
- //#endregion
50328
- //#region src/features/skills/antigravity-ide-skill.ts
50329
- /**
50330
- * Represents a Google Antigravity IDE skill directory (Antigravity 2.0).
50331
- *
50332
- * Shares all behavior with {@link AntigravitySharedSkill}. As of Antigravity
50333
- * 2.0 the IDE reads its global skills from the shared `~/.gemini/config/skills/`
50334
- * location (the Skills doc lists `~/.gemini/config/skills/<skill-folder>/` as
50335
- * the global scope); the legacy `~/.gemini/antigravity/skills/` path is no
50336
- * longer used. It answers to the `antigravity-ide` target.
50337
- */
50338
- var AntigravityIdeSkill = class extends AntigravitySharedSkill {
50339
- static getGlobalSubdir() {
50340
- return ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR;
50341
- }
50342
- static getToolTarget() {
50343
- return "antigravity-ide";
50344
- }
50345
- };
50346
- //#endregion
50347
- //#region src/features/skills/antigravity-plugin-skill.ts
50348
- var AntigravityPluginSkill = class extends AntigravityIdeSkill {
50349
- static isTargetedByRulesyncSkill(rulesyncSkill) {
50350
- const targets = rulesyncSkill.getFrontmatter().targets;
50351
- return targets.includes("*") || targets.includes("antigravity-plugin");
50352
- }
50353
- static getSettablePaths() {
50354
- return { relativeDirPath: ANTIGRAVITY_PLUGIN_SKILLS_DIR };
50355
- }
50356
- };
50357
- //#endregion
50358
- //#region src/features/skills/augmentcode-skill.ts
50359
- const AugmentcodeSkillFrontmatterSchema = zod_mini.z.looseObject({
53016
+ //#region src/features/skills/bob-skill.ts
53017
+ const BobSkillFrontmatterSchema = zod_mini.z.looseObject({
50360
53018
  name: zod_mini.z.string(),
50361
53019
  description: zod_mini.z.string()
50362
53020
  });
50363
53021
  /**
50364
- * Represents an AugmentCode (Auggie CLI) skill directory.
50365
- * Auggie has native skill support — it reads `<name>/SKILL.md` directories under
50366
- * `.augment/skills/` (project) and `~/.augment/skills/` (global).
50367
- * See https://docs.augmentcode.com/cli/skills
53022
+ * Represents an IBM Bob skill directory.
53023
+ *
53024
+ * Bob discovers Anthropic-style `<name>/SKILL.md` directories under
53025
+ * `<project>/.bob/skills/` (project scope) and `~/.bob/skills/` (user scope);
53026
+ * the project copy wins on a name clash. The frontmatter requires `name`
53027
+ * (matching the directory name) and `description`; supporting files next to
53028
+ * `SKILL.md` are carried along.
53029
+ *
53030
+ * @see https://bob.ibm.com/docs/ide/features/skills
50368
53031
  */
50369
- var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50370
- constructor({ outputRoot = process.cwd(), relativeDirPath = AugmentcodeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
53032
+ var BobSkill = class BobSkill extends ToolSkill {
53033
+ constructor({ outputRoot = process.cwd(), relativeDirPath = BOB_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
50371
53034
  super({
50372
53035
  outputRoot,
50373
53036
  relativeDirPath,
@@ -50386,38 +53049,40 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50386
53049
  }
50387
53050
  }
50388
53051
  static getSettablePaths({ global: _global = false } = {}) {
50389
- return {
50390
- relativeDirPath: AUGMENTCODE_SKILLS_DIR_PATH,
50391
- alternativeSkillRoots: [AUGMENTCODE_AGENTS_SKILLS_DIR_PATH]
50392
- };
53052
+ return { relativeDirPath: BOB_SKILLS_DIR_PATH };
50393
53053
  }
50394
53054
  getFrontmatter() {
50395
- return AugmentcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
53055
+ return BobSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
50396
53056
  }
50397
53057
  getBody() {
50398
53058
  return this.mainFile?.body ?? "";
50399
53059
  }
50400
53060
  validate() {
50401
- if (this.mainFile === void 0) return {
53061
+ if (!this.mainFile) return {
50402
53062
  success: false,
50403
53063
  error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
50404
53064
  };
50405
- const result = AugmentcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
53065
+ const result = BobSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
50406
53066
  if (!result.success) return {
50407
53067
  success: false,
50408
53068
  error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
50409
53069
  };
53070
+ if (result.data.name !== this.getDirName()) return {
53071
+ success: false,
53072
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
53073
+ };
50410
53074
  return {
50411
53075
  success: true,
50412
53076
  error: null
50413
53077
  };
50414
53078
  }
50415
53079
  toRulesyncSkill() {
50416
- const frontmatter = this.getFrontmatter();
53080
+ const { name, description, ...bobSection } = this.getFrontmatter();
50417
53081
  const rulesyncFrontmatter = {
50418
- name: frontmatter.name,
50419
- description: frontmatter.description,
50420
- targets: ["*"]
53082
+ name,
53083
+ description,
53084
+ targets: ["*"],
53085
+ ...Object.keys(bobSection).length > 0 && { bob: bobSection }
50421
53086
  };
50422
53087
  return new RulesyncSkill({
50423
53088
  outputRoot: this.outputRoot,
@@ -50431,17 +53096,19 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50431
53096
  });
50432
53097
  }
50433
53098
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
50434
- const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
53099
+ const settablePaths = BobSkill.getSettablePaths({ global });
50435
53100
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
50436
- const augmentcodeFrontmatter = {
53101
+ const { name: _sectionName, description: _sectionDescription, ...bobSection } = rulesyncFrontmatter.bob ?? {};
53102
+ const bobFrontmatter = {
53103
+ ...bobSection,
50437
53104
  name: rulesyncFrontmatter.name,
50438
53105
  description: rulesyncFrontmatter.description
50439
53106
  };
50440
- return new AugmentcodeSkill({
53107
+ return new BobSkill({
50441
53108
  outputRoot,
50442
53109
  relativeDirPath: settablePaths.relativeDirPath,
50443
- dirName: rulesyncSkill.getDirName(),
50444
- frontmatter: augmentcodeFrontmatter,
53110
+ dirName: bobFrontmatter.name,
53111
+ frontmatter: bobFrontmatter,
50445
53112
  body: rulesyncSkill.getBody(),
50446
53113
  otherFiles: rulesyncSkill.getOtherFiles(),
50447
53114
  validate,
@@ -50450,19 +53117,23 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50450
53117
  }
50451
53118
  static isTargetedByRulesyncSkill(rulesyncSkill) {
50452
53119
  const targets = rulesyncSkill.getFrontmatter().targets;
50453
- return targets.includes("*") || targets.includes("augmentcode");
53120
+ return targets.includes("*") || targets.includes("bob");
50454
53121
  }
50455
53122
  static async fromDir(params) {
50456
53123
  const loaded = await this.loadSkillDirContent({
50457
53124
  ...params,
50458
- getSettablePaths: AugmentcodeSkill.getSettablePaths
53125
+ getSettablePaths: BobSkill.getSettablePaths
50459
53126
  });
50460
- const result = AugmentcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
53127
+ const result = BobSkillFrontmatterSchema.safeParse(loaded.frontmatter);
50461
53128
  if (!result.success) {
50462
53129
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
50463
53130
  throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
50464
53131
  }
50465
- return new AugmentcodeSkill({
53132
+ if (result.data.name !== loaded.dirName) {
53133
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
53134
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
53135
+ }
53136
+ return new BobSkill({
50466
53137
  outputRoot: loaded.outputRoot,
50467
53138
  relativeDirPath: loaded.relativeDirPath,
50468
53139
  dirName: loaded.dirName,
@@ -50474,10 +53145,9 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
50474
53145
  });
50475
53146
  }
50476
53147
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
50477
- const settablePaths = AugmentcodeSkill.getSettablePaths({ global });
50478
- return new AugmentcodeSkill({
53148
+ return new BobSkill({
50479
53149
  outputRoot,
50480
- relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
53150
+ relativeDirPath,
50481
53151
  dirName,
50482
53152
  frontmatter: {
50483
53153
  name: "",
@@ -51422,7 +54092,167 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51422
54092
  return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
51423
54093
  }
51424
54094
  getFrontmatter() {
51425
- return CopilotSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
54095
+ return CopilotSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
54096
+ }
54097
+ getBody() {
54098
+ return this.mainFile?.body ?? "";
54099
+ }
54100
+ validate() {
54101
+ if (!this.mainFile) return {
54102
+ success: false,
54103
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
54104
+ };
54105
+ const result = CopilotSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
54106
+ if (!result.success) return {
54107
+ success: false,
54108
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
54109
+ };
54110
+ return {
54111
+ success: true,
54112
+ error: null
54113
+ };
54114
+ }
54115
+ toRulesyncSkill() {
54116
+ const { name, description, ...copilotSection } = this.getFrontmatter();
54117
+ const rulesyncFrontmatter = {
54118
+ name,
54119
+ description,
54120
+ targets: ["*"],
54121
+ ...Object.keys(copilotSection).length > 0 && { copilot: copilotSection }
54122
+ };
54123
+ return new RulesyncSkill({
54124
+ outputRoot: this.outputRoot,
54125
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
54126
+ dirName: this.getDirName(),
54127
+ frontmatter: rulesyncFrontmatter,
54128
+ body: this.getBody(),
54129
+ otherFiles: this.getOtherFiles(),
54130
+ validate: true,
54131
+ global: this.global
54132
+ });
54133
+ }
54134
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
54135
+ const settablePaths = CopilotSkill.getSettablePaths({ global });
54136
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
54137
+ const copilotSection = rulesyncFrontmatter.copilot;
54138
+ const resolvedUserInvocable = resolveUserInvocable({
54139
+ rootFrontmatter: rulesyncFrontmatter,
54140
+ section: copilotSection
54141
+ });
54142
+ const resolvedDisableModelInvocation = resolveDisableModelInvocation({
54143
+ rootFrontmatter: rulesyncFrontmatter,
54144
+ section: copilotSection
54145
+ });
54146
+ const license = resolveLicense({
54147
+ rootFrontmatter: rulesyncFrontmatter,
54148
+ section: copilotSection
54149
+ });
54150
+ const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotFields } = copilotSection ?? {};
54151
+ const copilotFrontmatter = {
54152
+ ...copilotFields,
54153
+ name: rulesyncFrontmatter.name,
54154
+ description: rulesyncFrontmatter.description,
54155
+ ...license !== void 0 && { license },
54156
+ ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
54157
+ ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
54158
+ };
54159
+ return new CopilotSkill({
54160
+ outputRoot,
54161
+ relativeDirPath: settablePaths.relativeDirPath,
54162
+ dirName: rulesyncSkill.getDirName(),
54163
+ frontmatter: copilotFrontmatter,
54164
+ body: rulesyncSkill.getBody(),
54165
+ otherFiles: rulesyncSkill.getOtherFiles(),
54166
+ validate,
54167
+ global
54168
+ });
54169
+ }
54170
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
54171
+ const targets = rulesyncSkill.getFrontmatter().targets;
54172
+ return targets.includes("*") || targets.includes("copilot");
54173
+ }
54174
+ static async fromDir(params) {
54175
+ const loaded = await this.loadSkillDirContent({
54176
+ ...params,
54177
+ getSettablePaths: CopilotSkill.getSettablePaths
54178
+ });
54179
+ const result = CopilotSkillFrontmatterSchema.safeParse(loaded.frontmatter);
54180
+ if (!result.success) {
54181
+ const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
54182
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
54183
+ }
54184
+ return new CopilotSkill({
54185
+ outputRoot: loaded.outputRoot,
54186
+ relativeDirPath: loaded.relativeDirPath,
54187
+ dirName: loaded.dirName,
54188
+ frontmatter: result.data,
54189
+ body: loaded.body,
54190
+ otherFiles: loaded.otherFiles,
54191
+ validate: true,
54192
+ global: loaded.global
54193
+ });
54194
+ }
54195
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
54196
+ const settablePaths = CopilotSkill.getSettablePaths({ global });
54197
+ return new CopilotSkill({
54198
+ outputRoot,
54199
+ relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
54200
+ dirName,
54201
+ frontmatter: {
54202
+ name: "",
54203
+ description: ""
54204
+ },
54205
+ body: "",
54206
+ otherFiles: [],
54207
+ validate: false,
54208
+ global
54209
+ });
54210
+ }
54211
+ };
54212
+ //#endregion
54213
+ //#region src/features/skills/copilotcli-skill.ts
54214
+ const CopilotcliSkillFrontmatterSchema = zod_mini.z.looseObject({
54215
+ name: zod_mini.z.string(),
54216
+ description: zod_mini.z.string(),
54217
+ license: zod_mini.z.optional(zod_mini.z.string()),
54218
+ "allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
54219
+ "argument-hint": zod_mini.z.optional(zod_mini.z.string()),
54220
+ "user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
54221
+ "disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
54222
+ });
54223
+ /**
54224
+ * Represents a GitHub Copilot CLI skill directory.
54225
+ *
54226
+ * Copilot CLI discovers project skills from `.github/skills/` (shared with the
54227
+ * Copilot IDE target) and personal/global skills from `~/.copilot/skills/`. Each
54228
+ * skill is a directory containing a `SKILL.md` file with `name`/`description`
54229
+ * frontmatter. https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills
54230
+ */
54231
+ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
54232
+ constructor({ outputRoot = process.cwd(), relativeDirPath = COPILOT_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
54233
+ super({
54234
+ outputRoot,
54235
+ relativeDirPath,
54236
+ dirName,
54237
+ mainFile: {
54238
+ name: SKILL_FILE_NAME,
54239
+ body,
54240
+ frontmatter: { ...frontmatter }
54241
+ },
54242
+ otherFiles,
54243
+ global
54244
+ });
54245
+ if (validate) {
54246
+ const result = this.validate();
54247
+ if (!result.success) throw result.error;
54248
+ }
54249
+ }
54250
+ static getSettablePaths(options) {
54251
+ if (options?.global) return { relativeDirPath: COPILOT_SKILLS_GLOBAL_DIR_PATH };
54252
+ return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
54253
+ }
54254
+ getFrontmatter() {
54255
+ return CopilotcliSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
51426
54256
  }
51427
54257
  getBody() {
51428
54258
  return this.mainFile?.body ?? "";
@@ -51432,7 +54262,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51432
54262
  success: false,
51433
54263
  error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
51434
54264
  };
51435
- const result = CopilotSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
54265
+ const result = CopilotcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
51436
54266
  if (!result.success) return {
51437
54267
  success: false,
51438
54268
  error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
@@ -51443,12 +54273,12 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51443
54273
  };
51444
54274
  }
51445
54275
  toRulesyncSkill() {
51446
- const { name, description, ...copilotSection } = this.getFrontmatter();
54276
+ const { name, description, ...copilotcliSection } = this.getFrontmatter();
51447
54277
  const rulesyncFrontmatter = {
51448
54278
  name,
51449
54279
  description,
51450
54280
  targets: ["*"],
51451
- ...Object.keys(copilotSection).length > 0 && { copilot: copilotSection }
54281
+ ...Object.keys(copilotcliSection).length > 0 && { copilotcli: copilotcliSection }
51452
54282
  };
51453
54283
  return new RulesyncSkill({
51454
54284
  outputRoot: this.outputRoot,
@@ -51462,35 +54292,35 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51462
54292
  });
51463
54293
  }
51464
54294
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
51465
- const settablePaths = CopilotSkill.getSettablePaths({ global });
54295
+ const settablePaths = CopilotcliSkill.getSettablePaths({ global });
51466
54296
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
51467
- const copilotSection = rulesyncFrontmatter.copilot;
54297
+ const copilotcliSection = rulesyncFrontmatter.copilotcli;
51468
54298
  const resolvedUserInvocable = resolveUserInvocable({
51469
54299
  rootFrontmatter: rulesyncFrontmatter,
51470
- section: copilotSection
54300
+ section: copilotcliSection
51471
54301
  });
51472
54302
  const resolvedDisableModelInvocation = resolveDisableModelInvocation({
51473
54303
  rootFrontmatter: rulesyncFrontmatter,
51474
- section: copilotSection
54304
+ section: copilotcliSection
51475
54305
  });
51476
54306
  const license = resolveLicense({
51477
54307
  rootFrontmatter: rulesyncFrontmatter,
51478
- section: copilotSection
54308
+ section: copilotcliSection
51479
54309
  });
51480
- const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotFields } = copilotSection ?? {};
51481
- const copilotFrontmatter = {
51482
- ...copilotFields,
54310
+ const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotcliFields } = copilotcliSection ?? {};
54311
+ const copilotcliFrontmatter = {
54312
+ ...copilotcliFields,
51483
54313
  name: rulesyncFrontmatter.name,
51484
54314
  description: rulesyncFrontmatter.description,
51485
54315
  ...license !== void 0 && { license },
51486
54316
  ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
51487
54317
  ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
51488
54318
  };
51489
- return new CopilotSkill({
54319
+ return new CopilotcliSkill({
51490
54320
  outputRoot,
51491
54321
  relativeDirPath: settablePaths.relativeDirPath,
51492
54322
  dirName: rulesyncSkill.getDirName(),
51493
- frontmatter: copilotFrontmatter,
54323
+ frontmatter: copilotcliFrontmatter,
51494
54324
  body: rulesyncSkill.getBody(),
51495
54325
  otherFiles: rulesyncSkill.getOtherFiles(),
51496
54326
  validate,
@@ -51499,19 +54329,19 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51499
54329
  }
51500
54330
  static isTargetedByRulesyncSkill(rulesyncSkill) {
51501
54331
  const targets = rulesyncSkill.getFrontmatter().targets;
51502
- return targets.includes("*") || targets.includes("copilot");
54332
+ return targets.includes("*") || targets.includes("copilotcli");
51503
54333
  }
51504
54334
  static async fromDir(params) {
51505
54335
  const loaded = await this.loadSkillDirContent({
51506
54336
  ...params,
51507
- getSettablePaths: CopilotSkill.getSettablePaths
54337
+ getSettablePaths: CopilotcliSkill.getSettablePaths
51508
54338
  });
51509
- const result = CopilotSkillFrontmatterSchema.safeParse(loaded.frontmatter);
54339
+ const result = CopilotcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
51510
54340
  if (!result.success) {
51511
54341
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
51512
54342
  throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
51513
54343
  }
51514
- return new CopilotSkill({
54344
+ return new CopilotcliSkill({
51515
54345
  outputRoot: loaded.outputRoot,
51516
54346
  relativeDirPath: loaded.relativeDirPath,
51517
54347
  dirName: loaded.dirName,
@@ -51523,8 +54353,8 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51523
54353
  });
51524
54354
  }
51525
54355
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
51526
- const settablePaths = CopilotSkill.getSettablePaths({ global });
51527
- return new CopilotSkill({
54356
+ const settablePaths = CopilotcliSkill.getSettablePaths({ global });
54357
+ return new CopilotcliSkill({
51528
54358
  outputRoot,
51529
54359
  relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
51530
54360
  dirName,
@@ -51540,26 +54370,25 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
51540
54370
  }
51541
54371
  };
51542
54372
  //#endregion
51543
- //#region src/features/skills/copilotcli-skill.ts
51544
- const CopilotcliSkillFrontmatterSchema = zod_mini.z.looseObject({
54373
+ //#region src/features/skills/cortexcode-skill.ts
54374
+ const CortexcodeSkillFrontmatterSchema = zod_mini.z.looseObject({
51545
54375
  name: zod_mini.z.string(),
51546
54376
  description: zod_mini.z.string(),
51547
- license: zod_mini.z.optional(zod_mini.z.string()),
51548
- "allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
51549
- "argument-hint": zod_mini.z.optional(zod_mini.z.string()),
51550
- "user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
51551
- "disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
54377
+ /** Tool names the skill may use; omitted = every tool. */
54378
+ tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
51552
54379
  });
51553
54380
  /**
51554
- * Represents a GitHub Copilot CLI skill directory.
54381
+ * Represents a Snowflake Cortex Code skill directory.
51555
54382
  *
51556
- * Copilot CLI discovers project skills from `.github/skills/` (shared with the
51557
- * Copilot IDE target) and personal/global skills from `~/.copilot/skills/`. Each
51558
- * skill is a directory containing a `SKILL.md` file with `name`/`description`
51559
- * frontmatter. https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-skills
54383
+ * Cortex Code discovers Anthropic-style `<name>/SKILL.md` directories under
54384
+ * `<project>/.cortex/skills/` (project scope) and `~/.snowflake/cortex/skills/`
54385
+ * (user scope). The frontmatter requires `name` (matching the directory name)
54386
+ * and `description`; supporting files next to `SKILL.md` are carried along.
54387
+ *
54388
+ * @see https://docs.snowflake.com/en/user-guide/cortex-code/extensibility
51560
54389
  */
51561
- var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51562
- constructor({ outputRoot = process.cwd(), relativeDirPath = COPILOT_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
54390
+ var CortexcodeSkill = class CortexcodeSkill extends ToolSkill {
54391
+ constructor({ outputRoot = process.cwd(), relativeDirPath = CORTEXCODE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
51563
54392
  super({
51564
54393
  outputRoot,
51565
54394
  relativeDirPath,
@@ -51577,12 +54406,11 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51577
54406
  if (!result.success) throw result.error;
51578
54407
  }
51579
54408
  }
51580
- static getSettablePaths(options) {
51581
- if (options?.global) return { relativeDirPath: COPILOT_SKILLS_GLOBAL_DIR_PATH };
51582
- return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
54409
+ static getSettablePaths({ global = false } = {}) {
54410
+ return { relativeDirPath: global ? CORTEXCODE_GLOBAL_SKILLS_DIR_PATH : CORTEXCODE_SKILLS_DIR_PATH };
51583
54411
  }
51584
54412
  getFrontmatter() {
51585
- return CopilotcliSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
54413
+ return CortexcodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
51586
54414
  }
51587
54415
  getBody() {
51588
54416
  return this.mainFile?.body ?? "";
@@ -51592,23 +54420,27 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51592
54420
  success: false,
51593
54421
  error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
51594
54422
  };
51595
- const result = CopilotcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
54423
+ const result = CortexcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
51596
54424
  if (!result.success) return {
51597
54425
  success: false,
51598
54426
  error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
51599
54427
  };
54428
+ if (result.data.name !== this.getDirName()) return {
54429
+ success: false,
54430
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
54431
+ };
51600
54432
  return {
51601
54433
  success: true,
51602
54434
  error: null
51603
54435
  };
51604
54436
  }
51605
54437
  toRulesyncSkill() {
51606
- const { name, description, ...copilotcliSection } = this.getFrontmatter();
54438
+ const { name, description, ...cortexcodeSection } = this.getFrontmatter();
51607
54439
  const rulesyncFrontmatter = {
51608
54440
  name,
51609
54441
  description,
51610
54442
  targets: ["*"],
51611
- ...Object.keys(copilotcliSection).length > 0 && { copilotcli: copilotcliSection }
54443
+ ...Object.keys(cortexcodeSection).length > 0 && { cortexcode: cortexcodeSection }
51612
54444
  };
51613
54445
  return new RulesyncSkill({
51614
54446
  outputRoot: this.outputRoot,
@@ -51622,35 +54454,19 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51622
54454
  });
51623
54455
  }
51624
54456
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
51625
- const settablePaths = CopilotcliSkill.getSettablePaths({ global });
54457
+ const settablePaths = CortexcodeSkill.getSettablePaths({ global });
51626
54458
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
51627
- const copilotcliSection = rulesyncFrontmatter.copilotcli;
51628
- const resolvedUserInvocable = resolveUserInvocable({
51629
- rootFrontmatter: rulesyncFrontmatter,
51630
- section: copilotcliSection
51631
- });
51632
- const resolvedDisableModelInvocation = resolveDisableModelInvocation({
51633
- rootFrontmatter: rulesyncFrontmatter,
51634
- section: copilotcliSection
51635
- });
51636
- const license = resolveLicense({
51637
- rootFrontmatter: rulesyncFrontmatter,
51638
- section: copilotcliSection
51639
- });
51640
- const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, license: _license, ...copilotcliFields } = copilotcliSection ?? {};
51641
- const copilotcliFrontmatter = {
51642
- ...copilotcliFields,
54459
+ const { name: _sectionName, description: _sectionDescription, ...cortexcodeSection } = rulesyncFrontmatter.cortexcode ?? {};
54460
+ const cortexcodeFrontmatter = {
54461
+ ...cortexcodeSection,
51643
54462
  name: rulesyncFrontmatter.name,
51644
- description: rulesyncFrontmatter.description,
51645
- ...license !== void 0 && { license },
51646
- ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
51647
- ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
54463
+ description: rulesyncFrontmatter.description
51648
54464
  };
51649
- return new CopilotcliSkill({
54465
+ return new CortexcodeSkill({
51650
54466
  outputRoot,
51651
54467
  relativeDirPath: settablePaths.relativeDirPath,
51652
- dirName: rulesyncSkill.getDirName(),
51653
- frontmatter: copilotcliFrontmatter,
54468
+ dirName: cortexcodeFrontmatter.name,
54469
+ frontmatter: cortexcodeFrontmatter,
51654
54470
  body: rulesyncSkill.getBody(),
51655
54471
  otherFiles: rulesyncSkill.getOtherFiles(),
51656
54472
  validate,
@@ -51659,19 +54475,23 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51659
54475
  }
51660
54476
  static isTargetedByRulesyncSkill(rulesyncSkill) {
51661
54477
  const targets = rulesyncSkill.getFrontmatter().targets;
51662
- return targets.includes("*") || targets.includes("copilotcli");
54478
+ return targets.includes("*") || targets.includes("cortexcode");
51663
54479
  }
51664
54480
  static async fromDir(params) {
51665
54481
  const loaded = await this.loadSkillDirContent({
51666
54482
  ...params,
51667
- getSettablePaths: CopilotcliSkill.getSettablePaths
54483
+ getSettablePaths: CortexcodeSkill.getSettablePaths
51668
54484
  });
51669
- const result = CopilotcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
54485
+ const result = CortexcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
51670
54486
  if (!result.success) {
51671
54487
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
51672
54488
  throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
51673
54489
  }
51674
- return new CopilotcliSkill({
54490
+ if (result.data.name !== loaded.dirName) {
54491
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
54492
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
54493
+ }
54494
+ return new CortexcodeSkill({
51675
54495
  outputRoot: loaded.outputRoot,
51676
54496
  relativeDirPath: loaded.relativeDirPath,
51677
54497
  dirName: loaded.dirName,
@@ -51683,10 +54503,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
51683
54503
  });
51684
54504
  }
51685
54505
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
51686
- const settablePaths = CopilotcliSkill.getSettablePaths({ global });
51687
- return new CopilotcliSkill({
54506
+ return new CortexcodeSkill({
51688
54507
  outputRoot,
51689
- relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
54508
+ relativeDirPath,
51690
54509
  dirName,
51691
54510
  frontmatter: {
51692
54511
  name: "",
@@ -55010,6 +57829,154 @@ var RooSkill = class RooSkill extends ToolSkill {
55010
57829
  }
55011
57830
  };
55012
57831
  //#endregion
57832
+ //#region src/features/skills/tabnine-skill.ts
57833
+ const TabnineSkillFrontmatterSchema = zod_mini.z.looseObject({
57834
+ name: zod_mini.z.string(),
57835
+ description: zod_mini.z.string()
57836
+ });
57837
+ /**
57838
+ * Represents a Tabnine CLI skill directory.
57839
+ *
57840
+ * Tabnine CLI discovers Anthropic-style `<name>/SKILL.md` directories under
57841
+ * `<project>/.tabnine/agent/skills/` (workspace scope) and
57842
+ * `~/.tabnine/agent/skills/` (user scope); the workspace copy wins on a name
57843
+ * clash. The frontmatter requires `name` (matching the directory name) and
57844
+ * `description`; supporting files next to `SKILL.md` are carried along.
57845
+ *
57846
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/agent-skills
57847
+ */
57848
+ var TabnineSkill = class TabnineSkill extends ToolSkill {
57849
+ constructor({ outputRoot = process.cwd(), relativeDirPath = TABNINE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
57850
+ super({
57851
+ outputRoot,
57852
+ relativeDirPath,
57853
+ dirName,
57854
+ mainFile: {
57855
+ name: SKILL_FILE_NAME,
57856
+ body,
57857
+ frontmatter: { ...frontmatter }
57858
+ },
57859
+ otherFiles,
57860
+ global
57861
+ });
57862
+ if (validate) {
57863
+ const result = this.validate();
57864
+ if (!result.success) throw result.error;
57865
+ }
57866
+ }
57867
+ static getSettablePaths({ global: _global = false } = {}) {
57868
+ return { relativeDirPath: TABNINE_SKILLS_DIR_PATH };
57869
+ }
57870
+ getFrontmatter() {
57871
+ return TabnineSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
57872
+ }
57873
+ getBody() {
57874
+ return this.mainFile?.body ?? "";
57875
+ }
57876
+ validate() {
57877
+ if (!this.mainFile) return {
57878
+ success: false,
57879
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
57880
+ };
57881
+ const result = TabnineSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
57882
+ if (!result.success) return {
57883
+ success: false,
57884
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
57885
+ };
57886
+ if (result.data.name !== this.getDirName()) return {
57887
+ success: false,
57888
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
57889
+ };
57890
+ return {
57891
+ success: true,
57892
+ error: null
57893
+ };
57894
+ }
57895
+ toRulesyncSkill() {
57896
+ const { name, description, ...tabnineSection } = this.getFrontmatter();
57897
+ const rulesyncFrontmatter = {
57898
+ name,
57899
+ description,
57900
+ targets: ["*"],
57901
+ ...Object.keys(tabnineSection).length > 0 && { tabnine: tabnineSection }
57902
+ };
57903
+ return new RulesyncSkill({
57904
+ outputRoot: this.outputRoot,
57905
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
57906
+ dirName: this.getDirName(),
57907
+ frontmatter: rulesyncFrontmatter,
57908
+ body: this.getBody(),
57909
+ otherFiles: this.getOtherFiles(),
57910
+ validate: true,
57911
+ global: this.global
57912
+ });
57913
+ }
57914
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
57915
+ const settablePaths = TabnineSkill.getSettablePaths({ global });
57916
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
57917
+ const { name: _sectionName, description: _sectionDescription, ...tabnineSection } = rulesyncFrontmatter.tabnine ?? {};
57918
+ const tabnineFrontmatter = {
57919
+ ...tabnineSection,
57920
+ name: rulesyncFrontmatter.name,
57921
+ description: rulesyncFrontmatter.description
57922
+ };
57923
+ return new TabnineSkill({
57924
+ outputRoot,
57925
+ relativeDirPath: settablePaths.relativeDirPath,
57926
+ dirName: tabnineFrontmatter.name,
57927
+ frontmatter: tabnineFrontmatter,
57928
+ body: rulesyncSkill.getBody(),
57929
+ otherFiles: rulesyncSkill.getOtherFiles(),
57930
+ validate,
57931
+ global
57932
+ });
57933
+ }
57934
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
57935
+ const targets = rulesyncSkill.getFrontmatter().targets;
57936
+ return targets.includes("*") || targets.includes("tabnine");
57937
+ }
57938
+ static async fromDir(params) {
57939
+ const loaded = await this.loadSkillDirContent({
57940
+ ...params,
57941
+ getSettablePaths: TabnineSkill.getSettablePaths
57942
+ });
57943
+ const result = TabnineSkillFrontmatterSchema.safeParse(loaded.frontmatter);
57944
+ if (!result.success) {
57945
+ const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
57946
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
57947
+ }
57948
+ if (result.data.name !== loaded.dirName) {
57949
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
57950
+ throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
57951
+ }
57952
+ return new TabnineSkill({
57953
+ outputRoot: loaded.outputRoot,
57954
+ relativeDirPath: loaded.relativeDirPath,
57955
+ dirName: loaded.dirName,
57956
+ frontmatter: result.data,
57957
+ body: loaded.body,
57958
+ otherFiles: loaded.otherFiles,
57959
+ validate: true,
57960
+ global: loaded.global
57961
+ });
57962
+ }
57963
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
57964
+ return new TabnineSkill({
57965
+ outputRoot,
57966
+ relativeDirPath,
57967
+ dirName,
57968
+ frontmatter: {
57969
+ name: "",
57970
+ description: ""
57971
+ },
57972
+ body: "",
57973
+ otherFiles: [],
57974
+ validate: false,
57975
+ global
57976
+ });
57977
+ }
57978
+ };
57979
+ //#endregion
55013
57980
  //#region src/features/skills/takt-skill.ts
55014
57981
  /**
55015
57982
  * Skill generator for TAKT.
@@ -55898,6 +58865,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
55898
58865
  supportsGlobal: true
55899
58866
  }
55900
58867
  }],
58868
+ ["bob", {
58869
+ class: BobSkill,
58870
+ meta: {
58871
+ supportsProject: true,
58872
+ supportsSimulated: false,
58873
+ supportsGlobal: true
58874
+ }
58875
+ }],
55901
58876
  ["claudecode", {
55902
58877
  class: ClaudecodeSkill,
55903
58878
  meta: {
@@ -55954,6 +58929,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
55954
58929
  supportsGlobal: true
55955
58930
  }
55956
58931
  }],
58932
+ ["cortexcode", {
58933
+ class: CortexcodeSkill,
58934
+ meta: {
58935
+ supportsProject: true,
58936
+ supportsSimulated: false,
58937
+ supportsGlobal: true
58938
+ }
58939
+ }],
55957
58940
  ["crush", {
55958
58941
  class: CrushSkill,
55959
58942
  meta: {
@@ -56131,6 +59114,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
56131
59114
  supportsGlobal: true
56132
59115
  }
56133
59116
  }],
59117
+ ["tabnine", {
59118
+ class: TabnineSkill,
59119
+ meta: {
59120
+ supportsProject: true,
59121
+ supportsSimulated: false,
59122
+ supportsGlobal: true
59123
+ }
59124
+ }],
56134
59125
  ["takt", {
56135
59126
  class: TaktSkill,
56136
59127
  meta: {
@@ -58325,6 +61316,135 @@ var CopilotcliSubagent = class CopilotcliSubagent extends ToolSubagent {
58325
61316
  }
58326
61317
  };
58327
61318
  //#endregion
61319
+ //#region src/features/subagents/cortexcode-subagent.ts
61320
+ const CortexcodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
61321
+ name: zod_mini.z.string(),
61322
+ description: zod_mini.z.optional(zod_mini.z.string()),
61323
+ /** Tool names the subagent may use; omitted = every tool. */
61324
+ tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
61325
+ model: zod_mini.z.optional(zod_mini.z.string())
61326
+ });
61327
+ /**
61328
+ * Snowflake Cortex Code subagent: a Markdown file with YAML frontmatter under
61329
+ * `.cortex/agents/` (project) or `~/.snowflake/cortex/agents/` (user).
61330
+ */
61331
+ var CortexcodeSubagent = class CortexcodeSubagent extends ToolSubagent {
61332
+ frontmatter;
61333
+ body;
61334
+ constructor({ frontmatter, body, fileContent, ...rest }) {
61335
+ if (rest.validate !== false) {
61336
+ const result = CortexcodeSubagentFrontmatterSchema.safeParse(frontmatter);
61337
+ if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
61338
+ }
61339
+ super({
61340
+ ...rest,
61341
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
61342
+ });
61343
+ this.frontmatter = frontmatter;
61344
+ this.body = body;
61345
+ }
61346
+ static getSettablePaths({ global = false } = {}) {
61347
+ return { relativeDirPath: global ? CORTEXCODE_GLOBAL_AGENTS_DIR_PATH : CORTEXCODE_AGENTS_DIR_PATH };
61348
+ }
61349
+ getFrontmatter() {
61350
+ return this.frontmatter;
61351
+ }
61352
+ getBody() {
61353
+ return this.body;
61354
+ }
61355
+ toRulesyncSubagent() {
61356
+ const { name, description, ...rest } = this.frontmatter;
61357
+ return new RulesyncSubagent({
61358
+ outputRoot: ".",
61359
+ frontmatter: {
61360
+ targets: ["*"],
61361
+ name,
61362
+ description,
61363
+ cortexcode: { ...rest }
61364
+ },
61365
+ body: this.body,
61366
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
61367
+ relativeFilePath: this.getRelativeFilePath(),
61368
+ validate: true
61369
+ });
61370
+ }
61371
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
61372
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
61373
+ const cortexcodeSection = rulesyncFrontmatter.cortexcode ?? {};
61374
+ const cortexcodeSubagentFrontmatter = {
61375
+ name: rulesyncFrontmatter.name,
61376
+ description: rulesyncFrontmatter.description,
61377
+ ...cortexcodeSection
61378
+ };
61379
+ const body = rulesyncSubagent.getBody();
61380
+ const fileContent = stringifyFrontmatter(body, cortexcodeSubagentFrontmatter, { avoidBlockScalars: true });
61381
+ const paths = this.getSettablePaths({ global });
61382
+ return new CortexcodeSubagent({
61383
+ outputRoot,
61384
+ frontmatter: cortexcodeSubagentFrontmatter,
61385
+ body,
61386
+ relativeDirPath: paths.relativeDirPath,
61387
+ relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
61388
+ fileContent,
61389
+ validate,
61390
+ global
61391
+ });
61392
+ }
61393
+ validate() {
61394
+ if (!this.frontmatter) return {
61395
+ success: true,
61396
+ error: null
61397
+ };
61398
+ const result = CortexcodeSubagentFrontmatterSchema.safeParse(this.frontmatter);
61399
+ if (result.success) return {
61400
+ success: true,
61401
+ error: null
61402
+ };
61403
+ else return {
61404
+ success: false,
61405
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
61406
+ };
61407
+ }
61408
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
61409
+ return this.isTargetedByRulesyncSubagentDefault({
61410
+ rulesyncSubagent,
61411
+ toolTarget: "cortexcode"
61412
+ });
61413
+ }
61414
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
61415
+ const paths = this.getSettablePaths({ global });
61416
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
61417
+ const fileContent = await readFileContent(filePath);
61418
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
61419
+ const result = CortexcodeSubagentFrontmatterSchema.safeParse(frontmatter);
61420
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
61421
+ return new CortexcodeSubagent({
61422
+ outputRoot,
61423
+ relativeDirPath: paths.relativeDirPath,
61424
+ relativeFilePath,
61425
+ frontmatter: result.data,
61426
+ body: content.trim(),
61427
+ fileContent,
61428
+ validate,
61429
+ global
61430
+ });
61431
+ }
61432
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
61433
+ return new CortexcodeSubagent({
61434
+ outputRoot,
61435
+ relativeDirPath,
61436
+ relativeFilePath,
61437
+ frontmatter: {
61438
+ name: "",
61439
+ description: ""
61440
+ },
61441
+ body: "",
61442
+ fileContent: "",
61443
+ validate: false
61444
+ });
61445
+ }
61446
+ };
61447
+ //#endregion
58328
61448
  //#region src/features/subagents/cursor-subagent.ts
58329
61449
  const CursorSubagentFrontmatterSchema = zod_mini.z.looseObject({
58330
61450
  name: zod_mini.z.string(),
@@ -60777,6 +63897,145 @@ var RooSubagent = class extends ToolSubagent {
60777
63897
  }
60778
63898
  };
60779
63899
  //#endregion
63900
+ //#region src/features/subagents/tabnine-subagent.ts
63901
+ const TabnineSubagentFrontmatterSchema = zod_mini.z.looseObject({
63902
+ name: zod_mini.z.string(),
63903
+ description: zod_mini.z.optional(zod_mini.z.string()),
63904
+ /** "local" (default) runs inside the CLI; "remote" delegates to a Tabnine cloud agent. */
63905
+ kind: zod_mini.z.optional(zod_mini.z.string()),
63906
+ /** Built-in tool names the subagent may use; omitted = every tool. */
63907
+ tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
63908
+ model: zod_mini.z.optional(zod_mini.z.string()),
63909
+ /** Sampling temperature, 0.0-2.0. */
63910
+ temperature: zod_mini.z.optional(zod_mini.z.number()),
63911
+ /** Maximum agent turns (Tabnine default: 15). */
63912
+ max_turns: zod_mini.z.optional(zod_mini.z.number()),
63913
+ /** Wall-clock budget in minutes (Tabnine default: 5). */
63914
+ timeout_mins: zod_mini.z.optional(zod_mini.z.number())
63915
+ });
63916
+ /**
63917
+ * Tabnine CLI subagent: a Markdown file with YAML frontmatter under
63918
+ * `.tabnine/agent/agents/` (project) or `~/.tabnine/agent/agents/` (user).
63919
+ * Tabnine only loads these when `experimental.enableAgents` is `true` in its
63920
+ * settings; rulesync writes the files and leaves that flag to the user.
63921
+ */
63922
+ var TabnineSubagent = class TabnineSubagent extends ToolSubagent {
63923
+ frontmatter;
63924
+ body;
63925
+ constructor({ frontmatter, body, fileContent, ...rest }) {
63926
+ if (rest.validate !== false) {
63927
+ const result = TabnineSubagentFrontmatterSchema.safeParse(frontmatter);
63928
+ if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
63929
+ }
63930
+ super({
63931
+ ...rest,
63932
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
63933
+ });
63934
+ this.frontmatter = frontmatter;
63935
+ this.body = body;
63936
+ }
63937
+ static getSettablePaths(_options = {}) {
63938
+ return { relativeDirPath: TABNINE_AGENTS_DIR_PATH };
63939
+ }
63940
+ getFrontmatter() {
63941
+ return this.frontmatter;
63942
+ }
63943
+ getBody() {
63944
+ return this.body;
63945
+ }
63946
+ toRulesyncSubagent() {
63947
+ const { name, description, ...rest } = this.frontmatter;
63948
+ return new RulesyncSubagent({
63949
+ outputRoot: ".",
63950
+ frontmatter: {
63951
+ targets: ["*"],
63952
+ name,
63953
+ description,
63954
+ tabnine: { ...rest }
63955
+ },
63956
+ body: this.body,
63957
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
63958
+ relativeFilePath: this.getRelativeFilePath(),
63959
+ validate: true
63960
+ });
63961
+ }
63962
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
63963
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
63964
+ const tabnineSection = rulesyncFrontmatter.tabnine ?? {};
63965
+ const tabnineSubagentFrontmatter = {
63966
+ name: rulesyncFrontmatter.name,
63967
+ description: rulesyncFrontmatter.description,
63968
+ ...tabnineSection
63969
+ };
63970
+ const body = rulesyncSubagent.getBody();
63971
+ const fileContent = stringifyFrontmatter(body, tabnineSubagentFrontmatter, { avoidBlockScalars: true });
63972
+ const paths = this.getSettablePaths({ global });
63973
+ return new TabnineSubagent({
63974
+ outputRoot,
63975
+ frontmatter: tabnineSubagentFrontmatter,
63976
+ body,
63977
+ relativeDirPath: paths.relativeDirPath,
63978
+ relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
63979
+ fileContent,
63980
+ validate,
63981
+ global
63982
+ });
63983
+ }
63984
+ validate() {
63985
+ if (!this.frontmatter) return {
63986
+ success: true,
63987
+ error: null
63988
+ };
63989
+ const result = TabnineSubagentFrontmatterSchema.safeParse(this.frontmatter);
63990
+ if (result.success) return {
63991
+ success: true,
63992
+ error: null
63993
+ };
63994
+ else return {
63995
+ success: false,
63996
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
63997
+ };
63998
+ }
63999
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
64000
+ return this.isTargetedByRulesyncSubagentDefault({
64001
+ rulesyncSubagent,
64002
+ toolTarget: "tabnine"
64003
+ });
64004
+ }
64005
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
64006
+ const paths = this.getSettablePaths({ global });
64007
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
64008
+ const fileContent = await readFileContent(filePath);
64009
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
64010
+ const result = TabnineSubagentFrontmatterSchema.safeParse(frontmatter);
64011
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
64012
+ return new TabnineSubagent({
64013
+ outputRoot,
64014
+ relativeDirPath: paths.relativeDirPath,
64015
+ relativeFilePath,
64016
+ frontmatter: result.data,
64017
+ body: content.trim(),
64018
+ fileContent,
64019
+ validate,
64020
+ global
64021
+ });
64022
+ }
64023
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
64024
+ return new TabnineSubagent({
64025
+ outputRoot,
64026
+ relativeDirPath,
64027
+ relativeFilePath,
64028
+ frontmatter: {
64029
+ name: "",
64030
+ description: ""
64031
+ },
64032
+ body: "",
64033
+ fileContent: "",
64034
+ validate: false
64035
+ });
64036
+ }
64037
+ };
64038
+ //#endregion
60780
64039
  //#region src/features/subagents/takt-subagent.ts
60781
64040
  /**
60782
64041
  * Subagent generator for TAKT.
@@ -61408,6 +64667,15 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
61408
64667
  filePattern: "*.agent.md"
61409
64668
  }
61410
64669
  }],
64670
+ ["cortexcode", {
64671
+ class: CortexcodeSubagent,
64672
+ meta: {
64673
+ supportsProject: true,
64674
+ supportsSimulated: false,
64675
+ supportsGlobal: true,
64676
+ filePattern: "*.md"
64677
+ }
64678
+ }],
61411
64679
  ["cursor", {
61412
64680
  class: CursorSubagent,
61413
64681
  meta: {
@@ -61579,6 +64847,15 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
61579
64847
  filePattern: "*.md"
61580
64848
  }
61581
64849
  }],
64850
+ ["tabnine", {
64851
+ class: TabnineSubagent,
64852
+ meta: {
64853
+ supportsProject: true,
64854
+ supportsSimulated: false,
64855
+ supportsGlobal: true,
64856
+ filePattern: "*.md"
64857
+ }
64858
+ }],
61582
64859
  ["takt", {
61583
64860
  class: TaktSubagent,
61584
64861
  meta: {
@@ -63130,6 +66407,95 @@ var AugmentcodeRule = class AugmentcodeRule extends ToolRule {
63130
66407
  }
63131
66408
  };
63132
66409
  //#endregion
66410
+ //#region src/features/rules/bob-rule.ts
66411
+ /**
66412
+ * Rule generator for IBM Bob (Bob IDE and Bob Shell).
66413
+ *
66414
+ * Bob auto-loads plain-markdown instruction files with no frontmatter:
66415
+ *
66416
+ * - Project scope: the cross-tool `AGENTS.md` at the project root plus every
66417
+ * `.bob/rules/*.md` file (read recursively in alphabetical order).
66418
+ * - Global scope: `~/.bob/AGENTS.md` plus `~/.bob/rules/*.md`.
66419
+ *
66420
+ * The workspace files override the user files. Bob's mode-specific
66421
+ * `.bob/rules-{mode}/` directories are left alone: rulesync has no notion of
66422
+ * Bob's modes, and a rule that should apply everywhere belongs in `.bob/rules/`.
66423
+ *
66424
+ * @see https://bob.ibm.com/docs/ide/configuration/rules
66425
+ * @see https://bob.ibm.com/docs/shell/configuration/configuring (user-scoped `~/.bob/AGENTS.md`)
66426
+ */
66427
+ var BobRule = class BobRule extends ToolRule {
66428
+ static getSettablePaths({ global, excludeToolDir } = {}) {
66429
+ return {
66430
+ root: {
66431
+ relativeDirPath: global ? buildToolPath(BOB_DIR, ".", excludeToolDir) : ".",
66432
+ relativeFilePath: BOB_RULE_FILE_NAME
66433
+ },
66434
+ nonRoot: { relativeDirPath: buildToolPath(BOB_DIR, "rules", excludeToolDir) }
66435
+ };
66436
+ }
66437
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
66438
+ const paths = this.getSettablePaths({ global });
66439
+ if (relativeFilePath === paths.root.relativeFilePath) {
66440
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.root.relativeDirPath, paths.root.relativeFilePath));
66441
+ return new BobRule({
66442
+ outputRoot,
66443
+ relativeDirPath: paths.root.relativeDirPath,
66444
+ relativeFilePath: paths.root.relativeFilePath,
66445
+ fileContent,
66446
+ validate,
66447
+ root: true
66448
+ });
66449
+ }
66450
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.nonRoot.relativeDirPath, relativeFilePath));
66451
+ return new BobRule({
66452
+ outputRoot,
66453
+ relativeDirPath: paths.nonRoot.relativeDirPath,
66454
+ relativeFilePath,
66455
+ fileContent,
66456
+ validate,
66457
+ root: false
66458
+ });
66459
+ }
66460
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
66461
+ const paths = this.getSettablePaths({ global });
66462
+ return new BobRule(this.buildToolRuleParamsDefault({
66463
+ outputRoot,
66464
+ rulesyncRule,
66465
+ validate,
66466
+ rootPath: paths.root,
66467
+ nonRootPath: paths.nonRoot
66468
+ }));
66469
+ }
66470
+ toRulesyncRule() {
66471
+ return this.toRulesyncRuleDefault();
66472
+ }
66473
+ validate() {
66474
+ return {
66475
+ success: true,
66476
+ error: null
66477
+ };
66478
+ }
66479
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
66480
+ const paths = this.getSettablePaths({ global });
66481
+ const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath !== paths.nonRoot.relativeDirPath;
66482
+ return new BobRule({
66483
+ outputRoot,
66484
+ relativeDirPath,
66485
+ relativeFilePath,
66486
+ fileContent: "",
66487
+ validate: false,
66488
+ root: isRoot
66489
+ });
66490
+ }
66491
+ static isTargetedByRulesyncRule(rulesyncRule) {
66492
+ return this.isTargetedByRulesyncRuleDefault({
66493
+ rulesyncRule,
66494
+ toolTarget: "bob"
66495
+ });
66496
+ }
66497
+ };
66498
+ //#endregion
63133
66499
  //#region src/features/rules/claudecode-language-settings.ts
63134
66500
  /**
63135
66501
  * Claude Code's native home for the root `language` key of `rulesync.jsonc`.
@@ -64344,6 +67710,73 @@ var CopilotcliRule = class CopilotcliRule extends CopilotRule {
64344
67710
  }
64345
67711
  };
64346
67712
  //#endregion
67713
+ //#region src/features/rules/cortexcode-rule.ts
67714
+ var CortexcodeRule = class CortexcodeRule extends ToolRule {
67715
+ constructor({ fileContent, root, ...rest }) {
67716
+ super({
67717
+ ...rest,
67718
+ fileContent,
67719
+ root: root ?? false
67720
+ });
67721
+ }
67722
+ static getSettablePaths(_options = {}) {
67723
+ return { root: {
67724
+ relativeDirPath: ".",
67725
+ relativeFilePath: CORTEXCODE_RULE_FILE_NAME
67726
+ } };
67727
+ }
67728
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
67729
+ const { root } = this.getSettablePaths({ global });
67730
+ const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
67731
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
67732
+ return new CortexcodeRule({
67733
+ outputRoot,
67734
+ relativeDirPath: root.relativeDirPath,
67735
+ relativeFilePath: root.relativeFilePath,
67736
+ fileContent,
67737
+ validate,
67738
+ root: true
67739
+ });
67740
+ }
67741
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
67742
+ const { root } = this.getSettablePaths({ global });
67743
+ const isRoot = rulesyncRule.getFrontmatter().root ?? false;
67744
+ return new CortexcodeRule({
67745
+ outputRoot,
67746
+ relativeDirPath: root.relativeDirPath,
67747
+ relativeFilePath: root.relativeFilePath,
67748
+ fileContent: rulesyncRule.getBody(),
67749
+ validate,
67750
+ root: isRoot
67751
+ });
67752
+ }
67753
+ toRulesyncRule() {
67754
+ return this.toRulesyncRuleDefault();
67755
+ }
67756
+ validate() {
67757
+ return {
67758
+ success: true,
67759
+ error: null
67760
+ };
67761
+ }
67762
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
67763
+ return new CortexcodeRule({
67764
+ outputRoot,
67765
+ relativeDirPath,
67766
+ relativeFilePath,
67767
+ fileContent: "",
67768
+ validate: false,
67769
+ root: relativeFilePath === "AGENTS.md" && relativeDirPath === "."
67770
+ });
67771
+ }
67772
+ static isTargetedByRulesyncRule(rulesyncRule) {
67773
+ return this.isTargetedByRulesyncRuleDefault({
67774
+ rulesyncRule,
67775
+ toolTarget: "cortexcode"
67776
+ });
67777
+ }
67778
+ };
67779
+ //#endregion
64347
67780
  //#region src/features/rules/crush-rule.ts
64348
67781
  var CrushRule = class CrushRule extends ToolRule {
64349
67782
  constructor({ fileContent, root, ...rest }) {
@@ -67421,6 +70854,97 @@ var RovodevRule = class RovodevRule extends ToolRule {
67421
70854
  }
67422
70855
  };
67423
70856
  //#endregion
70857
+ //#region src/features/rules/tabnine-rule.ts
70858
+ /**
70859
+ * Rule generator for Tabnine CLI.
70860
+ *
70861
+ * - Project scope: `TABNINE.md` at the project root is the CLI's memory file
70862
+ * (loaded on every session, refreshed with `/memory reload`). Non-root
70863
+ * rules go to `.tabnine/guidelines/*.md`, the guideline directory the
70864
+ * Tabnine IDE agent reads; the CLI does not auto-load that directory, so
70865
+ * the processor lists the files from `TABNINE.md` (toon mode).
70866
+ * - Global scope: `~/.tabnine/agent/TABNINE.md` next to the other user-scoped
70867
+ * agent assets (the CLI documents no user-level memory file, so this path
70868
+ * mirrors the `~/.gemini/GEMINI.md` convention of the Gemini CLI it is
70869
+ * derived from) plus `~/.tabnine/guidelines/*.md`.
70870
+ *
70871
+ * Rule files are plain Markdown without frontmatter.
70872
+ *
70873
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-cli/features/settings/settings-reference (`context.fileName`, `/memory`)
70874
+ * @see https://docs.tabnine.com/main/getting-started/tabnine-agent/guidelines (`.tabnine/guidelines/`)
70875
+ */
70876
+ var TabnineRule = class TabnineRule extends ToolRule {
70877
+ static getSettablePaths({ global, excludeToolDir } = {}) {
70878
+ return {
70879
+ root: {
70880
+ relativeDirPath: global ? buildToolPath(TABNINE_AGENT_DIR_PATH, ".", excludeToolDir) : ".",
70881
+ relativeFilePath: TABNINE_RULE_FILE_NAME
70882
+ },
70883
+ nonRoot: { relativeDirPath: buildToolPath(TABNINE_DIR, "guidelines", excludeToolDir) }
70884
+ };
70885
+ }
70886
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
70887
+ const paths = this.getSettablePaths({ global });
70888
+ if (relativeFilePath === paths.root.relativeFilePath) {
70889
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.root.relativeDirPath, paths.root.relativeFilePath));
70890
+ return new TabnineRule({
70891
+ outputRoot,
70892
+ relativeDirPath: paths.root.relativeDirPath,
70893
+ relativeFilePath: paths.root.relativeFilePath,
70894
+ fileContent,
70895
+ validate,
70896
+ root: true
70897
+ });
70898
+ }
70899
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.nonRoot.relativeDirPath, relativeFilePath));
70900
+ return new TabnineRule({
70901
+ outputRoot,
70902
+ relativeDirPath: paths.nonRoot.relativeDirPath,
70903
+ relativeFilePath,
70904
+ fileContent,
70905
+ validate,
70906
+ root: false
70907
+ });
70908
+ }
70909
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
70910
+ const paths = this.getSettablePaths({ global });
70911
+ return new TabnineRule(this.buildToolRuleParamsDefault({
70912
+ outputRoot,
70913
+ rulesyncRule,
70914
+ validate,
70915
+ rootPath: paths.root,
70916
+ nonRootPath: paths.nonRoot
70917
+ }));
70918
+ }
70919
+ toRulesyncRule() {
70920
+ return this.toRulesyncRuleDefault();
70921
+ }
70922
+ validate() {
70923
+ return {
70924
+ success: true,
70925
+ error: null
70926
+ };
70927
+ }
70928
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
70929
+ const paths = this.getSettablePaths({ global });
70930
+ const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath !== paths.nonRoot.relativeDirPath;
70931
+ return new TabnineRule({
70932
+ outputRoot,
70933
+ relativeDirPath,
70934
+ relativeFilePath,
70935
+ fileContent: "",
70936
+ validate: false,
70937
+ root: isRoot
70938
+ });
70939
+ }
70940
+ static isTargetedByRulesyncRule(rulesyncRule) {
70941
+ return this.isTargetedByRulesyncRuleDefault({
70942
+ rulesyncRule,
70943
+ toolTarget: "tabnine"
70944
+ });
70945
+ }
70946
+ };
70947
+ //#endregion
67424
70948
  //#region src/features/rules/takt-rule.ts
67425
70949
  /**
67426
70950
  * Default facet directory for TAKT rule files.
@@ -67983,6 +71507,14 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
67983
71507
  ruleDiscoveryMode: "toon"
67984
71508
  }
67985
71509
  }],
71510
+ ["bob", {
71511
+ class: BobRule,
71512
+ meta: {
71513
+ extension: "md",
71514
+ supportsGlobal: true,
71515
+ ruleDiscoveryMode: "auto"
71516
+ }
71517
+ }],
67986
71518
  ["claudecode", {
67987
71519
  class: ClaudecodeRule,
67988
71520
  meta: {
@@ -68046,6 +71578,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
68046
71578
  ruleDiscoveryMode: "auto"
68047
71579
  }
68048
71580
  }],
71581
+ ["cortexcode", {
71582
+ class: CortexcodeRule,
71583
+ meta: {
71584
+ extension: "md",
71585
+ supportsGlobal: false,
71586
+ ruleDiscoveryMode: "auto",
71587
+ collisionPolicy: "fold"
71588
+ }
71589
+ }],
68049
71590
  ["crush", {
68050
71591
  class: CrushRule,
68051
71592
  meta: {
@@ -68252,6 +71793,14 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
68252
71793
  localRootFileName: "AGENTS.local.md"
68253
71794
  }
68254
71795
  }],
71796
+ ["tabnine", {
71797
+ class: TabnineRule,
71798
+ meta: {
71799
+ extension: "md",
71800
+ supportsGlobal: true,
71801
+ ruleDiscoveryMode: "toon"
71802
+ }
71803
+ }],
68255
71804
  ["takt", {
68256
71805
  class: TaktRule,
68257
71806
  meta: {
@@ -71889,6 +75438,18 @@ Object.defineProperty(exports, "RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH", {
71889
75438
  return RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH;
71890
75439
  }
71891
75440
  });
75441
+ Object.defineProperty(exports, "RULESYNC_USER_CONFIG_DIR_NAME", {
75442
+ enumerable: true,
75443
+ get: function() {
75444
+ return RULESYNC_USER_CONFIG_DIR_NAME;
75445
+ }
75446
+ });
75447
+ Object.defineProperty(exports, "RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME", {
75448
+ enumerable: true,
75449
+ get: function() {
75450
+ return RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME;
75451
+ }
75452
+ });
71892
75453
  Object.defineProperty(exports, "RulesProcessor", {
71893
75454
  enumerable: true,
71894
75455
  get: function() {
@@ -72039,6 +75600,12 @@ Object.defineProperty(exports, "assertDirectoryIfExists", {
72039
75600
  return assertDirectoryIfExists;
72040
75601
  }
72041
75602
  });
75603
+ Object.defineProperty(exports, "assertTargetsFeaturesExclusive", {
75604
+ enumerable: true,
75605
+ get: function() {
75606
+ return assertTargetsFeaturesExclusive;
75607
+ }
75608
+ });
72042
75609
  Object.defineProperty(exports, "assertTreeContainsNoSymlinks", {
72043
75610
  enumerable: true,
72044
75611
  get: function() {