rulesync 16.11.0 → 16.13.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.
- package/README.md +1 -1
- package/dist/cli/index.cjs +478 -113
- package/dist/cli/index.js +478 -113
- package/dist/cli/index.js.map +1 -1
- package/dist/{import--_2cfZlH.cjs → import-ChnrtxiJ.cjs} +1105 -216
- package/dist/{import-Bgkf_jVN.js → import-Dswkc7Ub.js} +1091 -202
- package/dist/import-Dswkc7Ub.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +13 -13
- package/dist/import-Bgkf_jVN.js.map +0 -1
|
@@ -134,27 +134,27 @@ const isFeatureValueEnabled = (value) => {
|
|
|
134
134
|
};
|
|
135
135
|
//#endregion
|
|
136
136
|
//#region src/constants/rulesync-paths.ts
|
|
137
|
-
const { join: join$
|
|
137
|
+
const { join: join$289 } = node_path.posix;
|
|
138
138
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
139
139
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
140
140
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
141
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
142
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
143
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
144
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
145
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
146
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
147
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
148
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
149
|
-
join$
|
|
150
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
151
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
141
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
142
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$289(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
143
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
144
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
145
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
146
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
147
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
148
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
149
|
+
join$289(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
150
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
151
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
152
152
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
153
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
153
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
154
154
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
155
155
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
156
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
157
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
156
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
157
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$289(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
158
158
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
159
159
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
160
160
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -437,6 +437,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
437
437
|
"kiro-cli",
|
|
438
438
|
"kiro-ide",
|
|
439
439
|
"opencode",
|
|
440
|
+
"pi",
|
|
440
441
|
"qwencode",
|
|
441
442
|
"reasonix",
|
|
442
443
|
"rovodev",
|
|
@@ -891,6 +892,7 @@ const ErrorCodes = {
|
|
|
891
892
|
INIT_FAILED: "INIT_FAILED",
|
|
892
893
|
MCP_FAILED: "MCP_FAILED",
|
|
893
894
|
DOCTOR_FAILED: "DOCTOR_FAILED",
|
|
895
|
+
RELEASE_NOTES_FAILED: "RELEASE_NOTES_FAILED",
|
|
894
896
|
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
895
897
|
};
|
|
896
898
|
/**
|
|
@@ -1927,7 +1929,7 @@ function isPlainObject$1(value) {
|
|
|
1927
1929
|
/**
|
|
1928
1930
|
* Type guard to check if a value is an array of strings.
|
|
1929
1931
|
*/
|
|
1930
|
-
function isStringArray$
|
|
1932
|
+
function isStringArray$2(value) {
|
|
1931
1933
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1932
1934
|
}
|
|
1933
1935
|
//#endregion
|
|
@@ -2295,6 +2297,7 @@ const HOOK_EVENTS = [
|
|
|
2295
2297
|
"todoCreated",
|
|
2296
2298
|
"todoCompleted",
|
|
2297
2299
|
"stopFailure",
|
|
2300
|
+
"stopCancelled",
|
|
2298
2301
|
"instructionsLoaded",
|
|
2299
2302
|
"userPromptExpansion",
|
|
2300
2303
|
"postToolBatch",
|
|
@@ -2477,34 +2480,52 @@ const AMP_HOOK_EVENTS = [
|
|
|
2477
2480
|
];
|
|
2478
2481
|
/**
|
|
2479
2482
|
* Hook events supported by Cline's file-based hooks. Cline resolves one
|
|
2480
|
-
* executable per lifecycle event from its hooks directory, and the
|
|
2481
|
-
*
|
|
2482
|
-
*
|
|
2483
|
-
*
|
|
2484
|
-
* `
|
|
2483
|
+
* executable per lifecycle event from its hooks directory, and the accepted
|
|
2484
|
+
* event names come from two runtimes that read the same directory:
|
|
2485
|
+
*
|
|
2486
|
+
* - The VS Code extension fixes them in `VALID_HOOK_TYPES`
|
|
2487
|
+
* (`apps/vscode/src/core/hooks/utils.ts`): `TaskStart`, `TaskResume`,
|
|
2488
|
+
* `TaskCancel`, `TaskComplete`, `PreToolUse`, `PostToolUse`,
|
|
2489
|
+
* `UserPromptSubmit`, `Notification` and `PreCompact`.
|
|
2490
|
+
* - The SDK/CLI fixes them in `HookConfigFileName`
|
|
2491
|
+
* (`sdk/packages/core/src/hooks/hook-file-config.ts`), which drops
|
|
2492
|
+
* `Notification` but adds `TaskError` (→ `agent_error`) and
|
|
2493
|
+
* `SessionShutdown` (→ `session_shutdown`).
|
|
2494
|
+
*
|
|
2495
|
+
* This set is the union, because `.clinerules/hooks` is in both runtimes'
|
|
2496
|
+
* search paths and a script named for an event the running one does not know
|
|
2497
|
+
* is simply never spawned. That holds for unknown *names* only: for an event a
|
|
2498
|
+
* runtime does know, the SDK/CLI spawns both the extensionless script and its
|
|
2499
|
+
* `.ps1` twin, so each generated script opens with a guard that stands down on
|
|
2500
|
+
* the platform the other one owns — see `generateClineHookScript` and
|
|
2501
|
+
* `generateClineHookPowerShellScript`.
|
|
2485
2502
|
*
|
|
2486
2503
|
* `TaskResume` and `TaskCancel` have no canonical counterpart and stay
|
|
2487
2504
|
* unmapped rather than being approximated by `sessionEnd` / `stop`, whose
|
|
2488
2505
|
* semantics differ.
|
|
2489
2506
|
*
|
|
2490
2507
|
* @see https://github.com/cline/cline/blob/main/apps/vscode/src/core/hooks/utils.ts
|
|
2508
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-config.ts
|
|
2491
2509
|
*/
|
|
2492
2510
|
const CLINE_HOOK_EVENTS = [
|
|
2493
2511
|
"sessionStart",
|
|
2512
|
+
"sessionEnd",
|
|
2494
2513
|
"preToolUse",
|
|
2495
2514
|
"postToolUse",
|
|
2496
2515
|
"beforeSubmitPrompt",
|
|
2497
2516
|
"preCompact",
|
|
2498
2517
|
"notification",
|
|
2499
|
-
"taskCompleted"
|
|
2518
|
+
"taskCompleted",
|
|
2519
|
+
"afterError"
|
|
2500
2520
|
];
|
|
2501
2521
|
/**
|
|
2502
2522
|
* Hook events supported by GitHub Copilot (cloud coding agent).
|
|
2503
2523
|
*
|
|
2504
2524
|
* The events rulesync writes to `.github/hooks/*.json`:
|
|
2505
2525
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted` ← `beforeSubmitPrompt`,
|
|
2506
|
-
* `preToolUse`, `postToolUse`, `agentStop` ← `stop`,
|
|
2507
|
-
* `subagentStop`, `errorOccurred` ← `afterError`,
|
|
2526
|
+
* `preToolUse`, `postToolUse`, `postToolUseFailure`, `agentStop` ← `stop`,
|
|
2527
|
+
* `subagentStart`, `subagentStop`, `errorOccurred` ← `afterError`,
|
|
2528
|
+
* `preCompact`, and `userPromptTransformed` ← `userPromptExpansion`.
|
|
2508
2529
|
*
|
|
2509
2530
|
* `preCompact` and `subagentStart` are authorable because the unified hooks
|
|
2510
2531
|
* reference's per-event "Cloud agent" column says both fire there. That column
|
|
@@ -2513,9 +2534,11 @@ const CLINE_HOOK_EVENTS = [
|
|
|
2513
2534
|
* undo that. `notification` and `permissionRequest` stay out because the same
|
|
2514
2535
|
* column is explicit that they do not fire on the cloud agent.
|
|
2515
2536
|
*
|
|
2516
|
-
*
|
|
2517
|
-
*
|
|
2518
|
-
* agent
|
|
2537
|
+
* `postToolUseFailure` and `userPromptTransformed` are shared with
|
|
2538
|
+
* {@link COPILOTCLI_HOOK_EVENTS}; the same column records both as firing on the
|
|
2539
|
+
* cloud agent, so they are authorable here too. The event surfaces overlap but
|
|
2540
|
+
* the config surfaces do not: `copilot` emits `command` hooks only, while the
|
|
2541
|
+
* CLI adapter also handles `http` and `prompt`.
|
|
2519
2542
|
*
|
|
2520
2543
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
2521
2544
|
*/
|
|
@@ -2525,11 +2548,13 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
2525
2548
|
"beforeSubmitPrompt",
|
|
2526
2549
|
"preToolUse",
|
|
2527
2550
|
"postToolUse",
|
|
2551
|
+
"postToolUseFailure",
|
|
2528
2552
|
"stop",
|
|
2529
2553
|
"subagentStart",
|
|
2530
2554
|
"subagentStop",
|
|
2531
2555
|
"afterError",
|
|
2532
|
-
"preCompact"
|
|
2556
|
+
"preCompact",
|
|
2557
|
+
"userPromptExpansion"
|
|
2533
2558
|
];
|
|
2534
2559
|
/**
|
|
2535
2560
|
* Hook events supported by the GitHub Copilot CLI (`copilotcli-hooks.ts`).
|
|
@@ -2848,6 +2873,7 @@ const GROKCLI_HOOK_EVENTS = [
|
|
|
2848
2873
|
"permissionDenied",
|
|
2849
2874
|
"stop",
|
|
2850
2875
|
"stopFailure",
|
|
2876
|
+
"stopCancelled",
|
|
2851
2877
|
"notification",
|
|
2852
2878
|
"subagentStart",
|
|
2853
2879
|
"subagentStop",
|
|
@@ -2923,16 +2949,10 @@ const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CAN
|
|
|
2923
2949
|
/**
|
|
2924
2950
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
2925
2951
|
*
|
|
2926
|
-
* Hermes validates hook events against a fixed `VALID_HOOKS` set
|
|
2927
|
-
*
|
|
2928
|
-
*
|
|
2929
|
-
*
|
|
2930
|
-
* `on_session_reset`, `subagent_start`, `subagent_stop`, `pre_gateway_dispatch`,
|
|
2931
|
-
* `pre_approval_request`, `post_approval_response`, `transform_tool_result`,
|
|
2932
|
-
* `transform_terminal_output`, `transform_llm_output`, and the three
|
|
2933
|
-
* `kanban_task_*` events. Only the events with a clean 1:1 canonical equivalent
|
|
2934
|
-
* are mapped here. All other native events round-trip through
|
|
2935
|
-
* `hermesagent.hooks`.
|
|
2952
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
2953
|
+
* of v0.20.2 (`v2026.8.16`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
2954
|
+
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
2955
|
+
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
2936
2956
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
2937
2957
|
*/
|
|
2938
2958
|
const HERMESAGENT_HOOK_EVENTS = [
|
|
@@ -2945,6 +2965,21 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
2945
2965
|
"subagentStart",
|
|
2946
2966
|
"subagentStop"
|
|
2947
2967
|
];
|
|
2968
|
+
/**
|
|
2969
|
+
* Hermes Agent's native `VALID_HOOKS` set, in upstream source order.
|
|
2970
|
+
*
|
|
2971
|
+
* The authority is `hermes_cli/plugins.py`'s `VALID_HOOKS`, not the docs: at
|
|
2972
|
+
* v0.20.2 `website/docs/user-guide/features/plugins.md` says "26 lifecycle
|
|
2973
|
+
* events" and then lists 30, omitting the five `on_kanban_*` worker/task/dispatch
|
|
2974
|
+
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
2975
|
+
* Shell hooks are gated on the same set.
|
|
2976
|
+
*
|
|
2977
|
+
* Verified against the tag `v2026.8.16` (v0.20.2): `VALID_HOOKS` holds 37
|
|
2978
|
+
* entries, grown from 23 at `v2026.8.3` (v0.20.0). This list holds 36 of them —
|
|
2979
|
+
* see {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
2980
|
+
*
|
|
2981
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py
|
|
2982
|
+
*/
|
|
2948
2983
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
2949
2984
|
"pre_tool_call",
|
|
2950
2985
|
"post_tool_call",
|
|
@@ -2953,6 +2988,10 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2953
2988
|
"transform_llm_output",
|
|
2954
2989
|
"pre_llm_call",
|
|
2955
2990
|
"post_llm_call",
|
|
2991
|
+
"on_stream_start",
|
|
2992
|
+
"on_stream_delta",
|
|
2993
|
+
"on_stream_end",
|
|
2994
|
+
"on_interim_message",
|
|
2956
2995
|
"pre_verify",
|
|
2957
2996
|
"pre_api_request",
|
|
2958
2997
|
"post_api_request",
|
|
@@ -2961,16 +3000,39 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2961
3000
|
"on_session_end",
|
|
2962
3001
|
"on_session_finalize",
|
|
2963
3002
|
"on_session_reset",
|
|
3003
|
+
"on_skill_lifecycle",
|
|
2964
3004
|
"subagent_start",
|
|
2965
3005
|
"subagent_stop",
|
|
2966
3006
|
"pre_gateway_dispatch",
|
|
2967
3007
|
"pre_approval_request",
|
|
2968
3008
|
"post_approval_response",
|
|
3009
|
+
"pre_transcription",
|
|
2969
3010
|
"kanban_task_claimed",
|
|
2970
3011
|
"kanban_task_completed",
|
|
2971
|
-
"kanban_task_blocked"
|
|
3012
|
+
"kanban_task_blocked",
|
|
3013
|
+
"on_kanban_worker_spawned",
|
|
3014
|
+
"on_kanban_worker_exited",
|
|
3015
|
+
"on_kanban_worker_stale_claim",
|
|
3016
|
+
"on_kanban_task_updated",
|
|
3017
|
+
"on_kanban_dispatch_tick",
|
|
3018
|
+
"gateway_platform_event",
|
|
3019
|
+
"pre_command"
|
|
2972
3020
|
];
|
|
2973
3021
|
/**
|
|
3022
|
+
* `VALID_HOOKS` entries a SHELL hook may not register for. `VALID_HOOKS` doubles
|
|
3023
|
+
* as the shell-hook allow-list, but `SHELL_UNSUPPORTED_HOOKS` is subtracted from
|
|
3024
|
+
* it first: shell hooks cannot return these events' directives, so
|
|
3025
|
+
* `_parse_hooks_block` refuses the registration with a warning rather than
|
|
3026
|
+
* accepting it and dropping the return value.
|
|
3027
|
+
*
|
|
3028
|
+
* rulesync only ever writes shell hooks, so these are excluded from
|
|
3029
|
+
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
3030
|
+
*
|
|
3031
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
3032
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/agent/shell_hooks.py — `_parse_hooks_block`
|
|
3033
|
+
*/
|
|
3034
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
3035
|
+
/**
|
|
2974
3036
|
* Map canonical camelCase event names to Hermes Agent's native `VALID_HOOKS`
|
|
2975
3037
|
* snake_case keys under the `hooks:` block of `~/.hermes/config.yaml`.
|
|
2976
3038
|
*/
|
|
@@ -3245,15 +3307,21 @@ const CANONICAL_TO_AMP_EVENT_NAMES = {
|
|
|
3245
3307
|
beforeSubmitPrompt: "agent.start",
|
|
3246
3308
|
stop: "agent.end"
|
|
3247
3309
|
};
|
|
3248
|
-
/**
|
|
3310
|
+
/**
|
|
3311
|
+
* Map canonical hook events to Cline's hook script file names — the union of
|
|
3312
|
+
* the VS Code extension's `VALID_HOOK_TYPES` and the SDK/CLI's
|
|
3313
|
+
* `HookConfigFileName`, see {@link CLINE_HOOK_EVENTS}.
|
|
3314
|
+
*/
|
|
3249
3315
|
const CANONICAL_TO_CLINE_EVENT_NAMES = {
|
|
3250
3316
|
sessionStart: "TaskStart",
|
|
3317
|
+
sessionEnd: "SessionShutdown",
|
|
3251
3318
|
preToolUse: "PreToolUse",
|
|
3252
3319
|
postToolUse: "PostToolUse",
|
|
3253
3320
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
3254
3321
|
preCompact: "PreCompact",
|
|
3255
3322
|
notification: "Notification",
|
|
3256
|
-
taskCompleted: "TaskComplete"
|
|
3323
|
+
taskCompleted: "TaskComplete",
|
|
3324
|
+
afterError: "TaskError"
|
|
3257
3325
|
};
|
|
3258
3326
|
/**
|
|
3259
3327
|
* Map canonical camelCase event names to Copilot camelCase.
|
|
@@ -3264,11 +3332,13 @@ const CANONICAL_TO_COPILOT_EVENT_NAMES = {
|
|
|
3264
3332
|
beforeSubmitPrompt: "userPromptSubmitted",
|
|
3265
3333
|
preToolUse: "preToolUse",
|
|
3266
3334
|
postToolUse: "postToolUse",
|
|
3335
|
+
postToolUseFailure: "postToolUseFailure",
|
|
3267
3336
|
stop: "agentStop",
|
|
3268
3337
|
subagentStart: "subagentStart",
|
|
3269
3338
|
subagentStop: "subagentStop",
|
|
3270
3339
|
afterError: "errorOccurred",
|
|
3271
|
-
preCompact: "preCompact"
|
|
3340
|
+
preCompact: "preCompact",
|
|
3341
|
+
userPromptExpansion: "userPromptTransformed"
|
|
3272
3342
|
};
|
|
3273
3343
|
/**
|
|
3274
3344
|
* Map Copilot camelCase event names to canonical camelCase.
|
|
@@ -3579,6 +3649,7 @@ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
|
3579
3649
|
permissionDenied: "PermissionDenied",
|
|
3580
3650
|
stop: "Stop",
|
|
3581
3651
|
stopFailure: "StopFailure",
|
|
3652
|
+
stopCancelled: "StopCancelled",
|
|
3582
3653
|
notification: "Notification",
|
|
3583
3654
|
subagentStart: "SubagentStart",
|
|
3584
3655
|
subagentStop: "SubagentStop",
|
|
@@ -3853,6 +3924,25 @@ const McpServerSchema = zod_mini.z.looseObject({
|
|
|
3853
3924
|
kiroAutoApprove: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3854
3925
|
kiroAutoBlock: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3855
3926
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
3927
|
+
/**
|
|
3928
|
+
* The canonical per-server tool allowlist.
|
|
3929
|
+
*
|
|
3930
|
+
* **Collision rule for adapters.** Several tools spell an allowlist natively
|
|
3931
|
+
* under a name that also exists here, so a single server entry can carry both
|
|
3932
|
+
* a canonical field and its native counterpart. What the *native* key means
|
|
3933
|
+
* decides the resolution — not which one was written first:
|
|
3934
|
+
*
|
|
3935
|
+
* - Native key is a redundant spelling of the same concept, and both lists are
|
|
3936
|
+
* additive → **merge** them, so a config that already spells the field
|
|
3937
|
+
* natively keeps working (`kiroAutoApprove`/`kiroAutoBlock` in
|
|
3938
|
+
* `kiro-mcp.ts`).
|
|
3939
|
+
* - Native key is the same concept but rulesync owns the generated value, so
|
|
3940
|
+
* keeping both would be ambiguous → the **canonical value wins** and the
|
|
3941
|
+
* native one is dropped with a warning (`tools` in `copilotcli-mcp.ts`).
|
|
3942
|
+
* - Native key of the same name means something else entirely → **refuse the
|
|
3943
|
+
* canonical value** rather than write a shape the tool misreads (`tools` in
|
|
3944
|
+
* `codexcli-mcp.ts`, where Codex reads it as a per-tool approval table).
|
|
3945
|
+
*/
|
|
3856
3946
|
enabledTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3857
3947
|
disabledTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
3858
3948
|
});
|
|
@@ -4695,8 +4785,65 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4695
4785
|
dangerouslyAllowAll: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4696
4786
|
});
|
|
4697
4787
|
/**
|
|
4788
|
+
* Tool-scoped override block for Pi Coding Agent. Pi has no allow/ask/deny rule
|
|
4789
|
+
* surface: the only repository-syncable tool gate is `defaultTools`, the list of
|
|
4790
|
+
* built-in tools enabled at startup (added in v0.84.2). It is an enable-list
|
|
4791
|
+
* rather than a canonical allow/deny rule set, so it is authored here rather
|
|
4792
|
+
* than mapped onto `permission`, mirroring `KimiCodePermissionsOverrideSchema`'s
|
|
4793
|
+
* `tools.enabled` and `VibePermissionsOverrideSchema`'s `enabled_tools`.
|
|
4794
|
+
*
|
|
4795
|
+
* An empty array is meaningful upstream — it starts Pi with no built-in tools
|
|
4796
|
+
* while keeping extension and SDK custom tools — so it is emitted as written.
|
|
4797
|
+
* CLI flags (`--tools`, `--no-tools`, `--no-builtin-tools`, `--exclude-tools`)
|
|
4798
|
+
* outrank the setting, and a project array **replaces** the global one rather
|
|
4799
|
+
* than merging with it.
|
|
4800
|
+
*
|
|
4801
|
+
* @example
|
|
4802
|
+
* { "defaultTools": ["bash", "edit", "write"] }
|
|
4803
|
+
* @see https://pi.dev/docs/latest/settings
|
|
4804
|
+
*/
|
|
4805
|
+
const PiPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4806
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4807
|
+
defaultTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4808
|
+
});
|
|
4809
|
+
/**
|
|
4810
|
+
* The Antigravity CLI's four documented `toolPermission` autonomy presets.
|
|
4811
|
+
* Exported so the import side of `antigravity-cli-permissions.ts` filters
|
|
4812
|
+
* against exactly the values the enum below accepts — an unrecognized value
|
|
4813
|
+
* carried in verbatim would fail validation of the *whole*
|
|
4814
|
+
* `.rulesync/permissions.jsonc`, not just that key.
|
|
4815
|
+
* @see https://antigravity.google/docs/cli/reference
|
|
4816
|
+
*/
|
|
4817
|
+
const ANTIGRAVITY_CLI_TOOL_PERMISSIONS = [
|
|
4818
|
+
"request-review",
|
|
4819
|
+
"proceed-in-sandbox",
|
|
4820
|
+
"always-proceed",
|
|
4821
|
+
"strict"
|
|
4822
|
+
];
|
|
4823
|
+
/**
|
|
4824
|
+
* The Antigravity CLI's three documented `artifactReviewPolicy` values. Shared
|
|
4825
|
+
* with the import filter for the same reason as
|
|
4826
|
+
* {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4827
|
+
* @see https://antigravity.google/docs/cli/settings
|
|
4828
|
+
*/
|
|
4829
|
+
const ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES = [
|
|
4830
|
+
"asks-for-review",
|
|
4831
|
+
"agent-decides",
|
|
4832
|
+
"always-proceed"
|
|
4833
|
+
];
|
|
4834
|
+
/**
|
|
4835
|
+
* The baseline execution modes the CLI persists as `agentMode`. Shared with the
|
|
4836
|
+
* import filter for the same reason as {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4837
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4838
|
+
*/
|
|
4839
|
+
const ANTIGRAVITY_CLI_AGENT_MODES = [
|
|
4840
|
+
"default",
|
|
4841
|
+
"accept-edits",
|
|
4842
|
+
"plan"
|
|
4843
|
+
];
|
|
4844
|
+
/**
|
|
4698
4845
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4699
|
-
* `settings.json` carries
|
|
4846
|
+
* `settings.json` carries five global autonomy/sandbox knobs outside the
|
|
4700
4847
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4701
4848
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4702
4849
|
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
@@ -4704,7 +4851,8 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4704
4851
|
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4705
4852
|
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4706
4853
|
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4707
|
-
* active workspace roots)
|
|
4854
|
+
* active workspace roots), and `agentMode` (the baseline execution mode a session
|
|
4855
|
+
* starts in — `default` / `accept-edits` / `plan`). Antigravity applies the allow/deny
|
|
4708
4856
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4709
4857
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4710
4858
|
* Fields placed here are merged onto the top level of
|
|
@@ -4712,27 +4860,27 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4712
4860
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4713
4861
|
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
4714
4862
|
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4715
|
-
* https://antigravity.google/docs/cli/sandbox
|
|
4716
|
-
* https://antigravity.google/docs/cli/settings
|
|
4863
|
+
* https://antigravity.google/docs/cli/sandbox,
|
|
4864
|
+
* https://antigravity.google/docs/cli/settings and
|
|
4865
|
+
* https://antigravity.google/docs/cli/modes.
|
|
4717
4866
|
*
|
|
4718
4867
|
* @example
|
|
4719
4868
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
4720
4869
|
*/
|
|
4721
4870
|
const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4722
4871
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4723
|
-
toolPermission: zod_mini.z.optional(zod_mini.z.enum(
|
|
4724
|
-
"request-review",
|
|
4725
|
-
"proceed-in-sandbox",
|
|
4726
|
-
"always-proceed",
|
|
4727
|
-
"strict"
|
|
4728
|
-
])),
|
|
4872
|
+
toolPermission: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_TOOL_PERMISSIONS)),
|
|
4729
4873
|
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4730
|
-
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum(
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4874
|
+
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES)),
|
|
4875
|
+
allowNonWorkspaceAccess: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4876
|
+
/**
|
|
4877
|
+
* The baseline execution mode the CLI starts a session in (Agent Mode), the
|
|
4878
|
+
* persisted form of the `/settings` panel's Agent Mode row. Only
|
|
4879
|
+
* `~/.gemini/antigravity-cli/settings.json` is read for it, which is why this
|
|
4880
|
+
* override stays global-only.
|
|
4881
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4882
|
+
*/
|
|
4883
|
+
agentMode: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_AGENT_MODES))
|
|
4736
4884
|
});
|
|
4737
4885
|
/**
|
|
4738
4886
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -5042,6 +5190,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
5042
5190
|
takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
|
|
5043
5191
|
amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
|
|
5044
5192
|
"antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema),
|
|
5193
|
+
pi: zod_mini.z.optional(PiPermissionsOverrideSchema),
|
|
5045
5194
|
augmentcode: zod_mini.z.optional(AugmentcodePermissionsOverrideSchema),
|
|
5046
5195
|
kiro: zod_mini.z.optional(KiroPermissionsOverrideSchema),
|
|
5047
5196
|
codexcli: zod_mini.z.optional(CodexcliPermissionsOverrideSchema),
|
|
@@ -5493,6 +5642,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
5493
5642
|
})),
|
|
5494
5643
|
cline: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
5495
5644
|
roo: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
5645
|
+
amp: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
5496
5646
|
devin: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5497
5647
|
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
5498
5648
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -5522,6 +5672,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
5522
5672
|
cursor: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5523
5673
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
5524
5674
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5675
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5525
5676
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
5526
5677
|
})),
|
|
5527
5678
|
factorydroid: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -5746,6 +5897,11 @@ async function getLocalSkillDirNames(outputRoot) {
|
|
|
5746
5897
|
* default with a per-target value. A defined section value (including `false`)
|
|
5747
5898
|
* always wins over the root default.
|
|
5748
5899
|
*
|
|
5900
|
+
* `devin` also consumes the root-level value — it maps `true` onto a user-only
|
|
5901
|
+
* `triggers` list (see `devin-skill.ts`) — but has no section key of the same
|
|
5902
|
+
* name, so it does not go through this helper. A `devin.triggers` section value
|
|
5903
|
+
* still overrides it.
|
|
5904
|
+
*
|
|
5749
5905
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5750
5906
|
*/
|
|
5751
5907
|
function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
@@ -5756,10 +5912,15 @@ function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
|
5756
5912
|
*
|
|
5757
5913
|
* The rulesync skill frontmatter exposes a root-level `user-invocable` default
|
|
5758
5914
|
* that applies to every tool supporting the flag (claudecode, copilot,
|
|
5759
|
-
* copilotcli, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
|
|
5915
|
+
* copilotcli, cursor, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
|
|
5760
5916
|
* per-target value. A defined section value (including `false`) always wins
|
|
5761
5917
|
* over the root default.
|
|
5762
5918
|
*
|
|
5919
|
+
* `devin` also consumes the root-level value — it maps `false` onto a
|
|
5920
|
+
* model-only `triggers` list (see `devin-skill.ts`) — but has no section key of
|
|
5921
|
+
* the same name, so it does not go through this helper. A `devin.triggers`
|
|
5922
|
+
* section value still overrides it.
|
|
5923
|
+
*
|
|
5763
5924
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5764
5925
|
*/
|
|
5765
5926
|
function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
@@ -7609,6 +7770,22 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7609
7770
|
}
|
|
7610
7771
|
}
|
|
7611
7772
|
},
|
|
7773
|
+
".pi/settings.json": {
|
|
7774
|
+
format: "json",
|
|
7775
|
+
invalidRootPolicy: "error",
|
|
7776
|
+
features: { permissions: {
|
|
7777
|
+
kind: "replace-owned-keys",
|
|
7778
|
+
ownedKeys: ["defaultTools"]
|
|
7779
|
+
} }
|
|
7780
|
+
},
|
|
7781
|
+
".pi/agent/settings.json": {
|
|
7782
|
+
format: "json",
|
|
7783
|
+
invalidRootPolicy: "error",
|
|
7784
|
+
features: { permissions: {
|
|
7785
|
+
kind: "replace-owned-keys",
|
|
7786
|
+
ownedKeys: ["defaultTools"]
|
|
7787
|
+
} }
|
|
7788
|
+
},
|
|
7612
7789
|
".github/copilot/settings.json": {
|
|
7613
7790
|
format: "json",
|
|
7614
7791
|
invalidRootPolicy: "error",
|
|
@@ -11725,7 +11902,8 @@ const KiloCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
11725
11902
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
11726
11903
|
agent: zod_mini.z.optional(zod_mini.z.string()),
|
|
11727
11904
|
subtask: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
11728
|
-
model: zod_mini.z.optional(zod_mini.z.string())
|
|
11905
|
+
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
11906
|
+
variant: zod_mini.z.optional(zod_mini.z.string())
|
|
11729
11907
|
});
|
|
11730
11908
|
var KiloCommand = class KiloCommand extends ToolCommand {
|
|
11731
11909
|
frontmatter;
|
|
@@ -12194,6 +12372,8 @@ const PI_RULE_FILE_NAME = "AGENTS.md";
|
|
|
12194
12372
|
const PI_RULE_OVERRIDE_FILE_NAME = "AGENTS.override.md";
|
|
12195
12373
|
const PI_APPEND_SYSTEM_FILE_NAME = "APPEND_SYSTEM.md";
|
|
12196
12374
|
const PI_HOOKS_FILE_NAME = "rulesync-hooks.ts";
|
|
12375
|
+
const PI_SETTINGS_FILE_NAME = "settings.json";
|
|
12376
|
+
const PI_AGENT_DIR_PATH = PI_AGENT_DIR;
|
|
12197
12377
|
//#endregion
|
|
12198
12378
|
//#region src/features/commands/pi-command.ts
|
|
12199
12379
|
/**
|
|
@@ -14118,7 +14298,7 @@ const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
|
14118
14298
|
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
14119
14299
|
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
14120
14300
|
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
14121
|
-
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
14301
|
+
const isEmittableArray = ({ value }) => isStringArray$1(value);
|
|
14122
14302
|
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
14123
14303
|
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
14124
14304
|
/**
|
|
@@ -14427,7 +14607,7 @@ function isStringRecord(value) {
|
|
|
14427
14607
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
14428
14608
|
return Object.values(value).every((v) => typeof v === "string");
|
|
14429
14609
|
}
|
|
14430
|
-
function isStringArray(value) {
|
|
14610
|
+
function isStringArray$1(value) {
|
|
14431
14611
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
14432
14612
|
}
|
|
14433
14613
|
/** Compare object values without letting key order decide the answer. */
|
|
@@ -14459,7 +14639,7 @@ function isSafeStringRecord(value) {
|
|
|
14459
14639
|
* skipped wholesale when that read fails.
|
|
14460
14640
|
*/
|
|
14461
14641
|
function isSafeStringArray(value) {
|
|
14462
|
-
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14642
|
+
return isStringArray$1(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14463
14643
|
}
|
|
14464
14644
|
/**
|
|
14465
14645
|
* A raw string kept only if the canonical field it would land in accepts it,
|
|
@@ -14501,7 +14681,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
14501
14681
|
return {
|
|
14502
14682
|
...url !== void 0 && { url },
|
|
14503
14683
|
...isStringRecord(headers) && { headers },
|
|
14504
|
-
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14684
|
+
...isStringArray$1(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14505
14685
|
};
|
|
14506
14686
|
}
|
|
14507
14687
|
if (hookType === "mcp_tool") {
|
|
@@ -15407,6 +15587,13 @@ function generateClineHookScript({ event, commands }) {
|
|
|
15407
15587
|
`# ${event} hook generated by rulesync — edit .rulesync/hooks.jsonc and regenerate.`,
|
|
15408
15588
|
`# ${CLINE_HOOK_SCRIPT_MARKER}`,
|
|
15409
15589
|
"",
|
|
15590
|
+
"case \"${OSTYPE:-$(uname -s 2>/dev/null || true)}\" in",
|
|
15591
|
+
" msys*|MSYS*|cygwin*|CYGWIN*|MINGW*|mingw*)",
|
|
15592
|
+
` printf '{"cancel": false, "contextModification": "", "errorMessage": ""}\\n'`,
|
|
15593
|
+
" exit 0",
|
|
15594
|
+
" ;;",
|
|
15595
|
+
"esac",
|
|
15596
|
+
"",
|
|
15410
15597
|
"payload=$(cat)",
|
|
15411
15598
|
"cancel=false",
|
|
15412
15599
|
"error_message=''",
|
|
@@ -15420,15 +15607,39 @@ function generateClineHookScript({ event, commands }) {
|
|
|
15420
15607
|
return lines.join("\n");
|
|
15421
15608
|
}
|
|
15422
15609
|
/**
|
|
15423
|
-
* The PowerShell twin of {@link generateClineHookScript}. On Windows
|
|
15424
|
-
* resolves only `<Event>.ps1` and runs it through `powershell -File`,
|
|
15425
|
-
* spellings are written and the platform picks one.
|
|
15610
|
+
* The PowerShell twin of {@link generateClineHookScript}. On Windows the VS Code
|
|
15611
|
+
* extension resolves only `<Event>.ps1` and runs it through `powershell -File`,
|
|
15612
|
+
* so both spellings are written and the platform picks one.
|
|
15613
|
+
*
|
|
15614
|
+
* The SDK/CLI runtime does not pick one. `listHookConfigFiles` dedupes by path,
|
|
15615
|
+
* so `TaskError` and `TaskError.ps1` are two entries naming the same event, and
|
|
15616
|
+
* `createHookCommandMap` appends both to that event's command list without a
|
|
15617
|
+
* per-event dedupe — it then runs every command in the list, spawning a `.ps1`
|
|
15618
|
+
* through `pwsh` on Unix too. That produced noise rather than a second
|
|
15619
|
+
* execution (the body shells out through `cmd /c`, which Unix has no such
|
|
15620
|
+
* thing), but it is still a failure reported on every fire.
|
|
15621
|
+
*
|
|
15622
|
+
* Hence the leading platform guard: off Windows the script answers with the
|
|
15623
|
+
* neutral success payload and exits, leaving the POSIX twin to do the work.
|
|
15624
|
+
* Its counterpart at the top of {@link generateClineHookScript} covers the
|
|
15625
|
+
* quadrant where the duplication is real — Windows with a POSIX shell.
|
|
15626
|
+
* `$IsWindows` only exists in PowerShell 6+, and is `$null` under the Windows
|
|
15627
|
+
* PowerShell 5.1 that `powershell -File` starts — so the guard tests that it is
|
|
15628
|
+
* defined *and* false, rather than `-not $IsWindows`, which would be true on
|
|
15629
|
+
* 5.1 and would no-op the script on the one platform it exists for.
|
|
15630
|
+
*
|
|
15631
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-hooks.ts
|
|
15426
15632
|
*/
|
|
15427
15633
|
function generateClineHookPowerShellScript({ event, commands }) {
|
|
15428
15634
|
const lines = [
|
|
15429
15635
|
`# ${event} hook generated by rulesync — edit .rulesync/hooks.jsonc and regenerate.`,
|
|
15430
15636
|
`# ${CLINE_HOOK_SCRIPT_MARKER}`,
|
|
15431
15637
|
"",
|
|
15638
|
+
"if ($null -ne $IsWindows -and -not $IsWindows) {",
|
|
15639
|
+
` Write-Output '{"cancel": false, "contextModification": "", "errorMessage": ""}'`,
|
|
15640
|
+
" exit 0",
|
|
15641
|
+
"}",
|
|
15642
|
+
"",
|
|
15432
15643
|
"$payload = [Console]::In.ReadToEnd()",
|
|
15433
15644
|
"$cancel = $false",
|
|
15434
15645
|
"$errorMessage = ''",
|
|
@@ -15453,7 +15664,7 @@ const MANAGED_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_CLINE_EVENT_NAMES
|
|
|
15453
15664
|
function parseManifest(fileContent) {
|
|
15454
15665
|
try {
|
|
15455
15666
|
const parsed = JSON.parse(fileContent);
|
|
15456
|
-
if (!isRecord$1(parsed) || !isStringArray$
|
|
15667
|
+
if (!isRecord$1(parsed) || !isStringArray$2(parsed.events)) return null;
|
|
15457
15668
|
return {
|
|
15458
15669
|
generatedBy: "rulesync",
|
|
15459
15670
|
events: parsed.events.filter((event) => MANAGED_EVENT_NAMES.has(event))
|
|
@@ -15490,10 +15701,14 @@ var ClineHookScript = class extends ToolFile {
|
|
|
15490
15701
|
* by the contract, so every generated script carries a marker line and a script
|
|
15491
15702
|
* without it is never overwritten.
|
|
15492
15703
|
*
|
|
15493
|
-
*
|
|
15494
|
-
*
|
|
15704
|
+
* The project hooks directory is in the search paths of both the VS Code
|
|
15705
|
+
* extension and the SDK/CLI, whose accepted event names differ slightly, so the
|
|
15706
|
+
* emitted set is their union ({@link CANONICAL_TO_CLINE_EVENT_NAMES}). Cline's
|
|
15707
|
+
* in-process hook surface (`AgentHooks` from `@cline/core`) is a separate
|
|
15708
|
+
* mechanism this adapter does not target.
|
|
15495
15709
|
*
|
|
15496
15710
|
* @see https://github.com/cline/cline/blob/main/apps/vscode/src/core/hooks/utils.ts
|
|
15711
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-config.ts
|
|
15497
15712
|
*/
|
|
15498
15713
|
var ClineHooks = class ClineHooks extends ToolHooks {
|
|
15499
15714
|
scriptsByEvent;
|
|
@@ -15648,6 +15863,11 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
15648
15863
|
numberPassthroughFields: [{
|
|
15649
15864
|
canonical: "additionalContextLimit",
|
|
15650
15865
|
tool: "additionalContextLimit"
|
|
15866
|
+
}],
|
|
15867
|
+
booleanPassthroughFields: [{
|
|
15868
|
+
canonical: "async",
|
|
15869
|
+
tool: "async",
|
|
15870
|
+
commandOnly: true
|
|
15651
15871
|
}]
|
|
15652
15872
|
};
|
|
15653
15873
|
/**
|
|
@@ -16774,6 +16994,16 @@ const FACTORYDROID_CONVERTER_CONFIG = {
|
|
|
16774
16994
|
subdividesGroup: true
|
|
16775
16995
|
}]
|
|
16776
16996
|
};
|
|
16997
|
+
/** Droid's nine event names, the keys a standalone `hooks.json` is made of. */
|
|
16998
|
+
const FACTORYDROID_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_FACTORYDROID_EVENT_NAMES));
|
|
16999
|
+
/**
|
|
17000
|
+
* Whether a parsed hooks file is the standalone shape — keyed directly by event
|
|
17001
|
+
* name — rather than the `settings.json` shape that wraps the same map in a
|
|
17002
|
+
* `hooks` key.
|
|
17003
|
+
*/
|
|
17004
|
+
function hasFactorydroidEventKey(parsed) {
|
|
17005
|
+
return Object.keys(parsed).some((key) => FACTORYDROID_EVENT_NAMES.has(key));
|
|
17006
|
+
}
|
|
16777
17007
|
var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
16778
17008
|
constructor(params) {
|
|
16779
17009
|
super({
|
|
@@ -16801,14 +17031,6 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
16801
17031
|
}
|
|
16802
17032
|
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
16803
17033
|
const paths = FactorydroidHooks.getSettablePaths({ global });
|
|
16804
|
-
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16805
|
-
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
16806
|
-
let settings;
|
|
16807
|
-
try {
|
|
16808
|
-
settings = JSON.parse(existingContent);
|
|
16809
|
-
} catch (error) {
|
|
16810
|
-
throw new Error(`Failed to parse existing Factory Droid hooks file at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
16811
|
-
}
|
|
16812
17034
|
const config = rulesyncHooks.getJson();
|
|
16813
17035
|
const factorydroidHooks = canonicalToToolHooks({
|
|
16814
17036
|
config,
|
|
@@ -16816,11 +17038,7 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
16816
17038
|
converterConfig: FACTORYDROID_CONVERTER_CONFIG,
|
|
16817
17039
|
logger
|
|
16818
17040
|
});
|
|
16819
|
-
const
|
|
16820
|
-
...settings,
|
|
16821
|
-
hooks: factorydroidHooks
|
|
16822
|
-
};
|
|
16823
|
-
const fileContent = JSON.stringify(merged, null, 2);
|
|
17041
|
+
const fileContent = JSON.stringify(factorydroidHooks, null, 2);
|
|
16824
17042
|
return new FactorydroidHooks({
|
|
16825
17043
|
outputRoot,
|
|
16826
17044
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -16830,14 +17048,14 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
16830
17048
|
});
|
|
16831
17049
|
}
|
|
16832
17050
|
toRulesyncHooks({ logger } = {}) {
|
|
16833
|
-
let
|
|
17051
|
+
let parsed;
|
|
16834
17052
|
try {
|
|
16835
|
-
|
|
17053
|
+
parsed = JSON.parse(this.getFileContent());
|
|
16836
17054
|
} catch (error) {
|
|
16837
17055
|
throw new Error(`Failed to parse Factory Droid hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
16838
17056
|
}
|
|
16839
17057
|
const hooks = toolHooksToCanonical({
|
|
16840
|
-
hooks:
|
|
17058
|
+
hooks: hasFactorydroidEventKey(parsed) ? parsed : parsed.hooks,
|
|
16841
17059
|
converterConfig: FACTORYDROID_CONVERTER_CONFIG,
|
|
16842
17060
|
logger
|
|
16843
17061
|
});
|
|
@@ -16972,18 +17190,7 @@ const GROKCLI_CONVERTER_CONFIG = {
|
|
|
16972
17190
|
tool: "env",
|
|
16973
17191
|
commandOnly: true
|
|
16974
17192
|
}],
|
|
16975
|
-
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
16976
|
-
"sessionStart",
|
|
16977
|
-
"sessionEnd",
|
|
16978
|
-
"beforeSubmitPrompt",
|
|
16979
|
-
"stop",
|
|
16980
|
-
"stopFailure",
|
|
16981
|
-
"notification",
|
|
16982
|
-
"subagentStart",
|
|
16983
|
-
"subagentStop",
|
|
16984
|
-
"preCompact",
|
|
16985
|
-
"postCompact"
|
|
16986
|
-
])
|
|
17193
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["stop", "beforeSubmitPrompt"])
|
|
16987
17194
|
};
|
|
16988
17195
|
/**
|
|
16989
17196
|
* Hooks generator for Grok CLI (xAI Grok Build).
|
|
@@ -17092,6 +17299,7 @@ const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["pre_tool_call", "po
|
|
|
17092
17299
|
const HERMESAGENT_FAIL_CLOSED_EVENT = "pre_tool_call";
|
|
17093
17300
|
const HERMESAGENT_CANONICAL_EVENTS = new Set(HERMESAGENT_HOOK_EVENTS);
|
|
17094
17301
|
const HERMESAGENT_NATIVE_EVENTS = new Set(HERMESAGENT_NATIVE_HOOK_EVENTS);
|
|
17302
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_EVENTS = new Set(HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS);
|
|
17095
17303
|
/**
|
|
17096
17304
|
* Whether an entry of the `hooks:` mapping is a hook-event list rather than one
|
|
17097
17305
|
* of its non-event siblings.
|
|
@@ -17180,7 +17388,8 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
17180
17388
|
}
|
|
17181
17389
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
17182
17390
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
17183
|
-
if (
|
|
17391
|
+
if (HERMESAGENT_SHELL_UNSUPPORTED_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is Python-plugin-only; Hermes Agent refuses it for shell hooks, so the generated entry will never run.`);
|
|
17392
|
+
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.20.2; preserving it for forward compatibility.`);
|
|
17184
17393
|
setHermesHookEntries({
|
|
17185
17394
|
result,
|
|
17186
17395
|
event: nativeEvent,
|
|
@@ -22304,7 +22513,7 @@ function isCodexStdioServer(config) {
|
|
|
22304
22513
|
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
22305
22514
|
*/
|
|
22306
22515
|
function isValidRenamedArray(key, value) {
|
|
22307
|
-
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$
|
|
22516
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$2(value);
|
|
22308
22517
|
}
|
|
22309
22518
|
/**
|
|
22310
22519
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
@@ -22824,6 +23033,49 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
22824
23033
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
22825
23034
|
};
|
|
22826
23035
|
/**
|
|
23036
|
+
* Copilot CLI's per-server tool allowlist. `tools` selects which of the
|
|
23037
|
+
* server's tools are exposed — `["*"]` (the default) for all, or a list of
|
|
23038
|
+
* names — so the canonical `enabledTools` is written under that name rather
|
|
23039
|
+
* than passed through as a key the CLI ignores.
|
|
23040
|
+
*
|
|
23041
|
+
* When a server carries both, the canonical `enabledTools` wins and the native
|
|
23042
|
+
* `tools` is dropped with a warning. rulesync owns the generated allowlist, and
|
|
23043
|
+
* the `tools` value most likely present is the upstream default `["*"]` — left
|
|
23044
|
+
* to win, it would silently discard a real allowlist in favour of "expose every
|
|
23045
|
+
* tool", which is the failure this mapping exists to prevent. That is the
|
|
23046
|
+
* "canonical wins" branch of the collision rule documented on `enabledTools` in
|
|
23047
|
+
* `src/types/mcp.ts`; `kiro-mcp.ts` merges instead (its native names are
|
|
23048
|
+
* redundant spellings of additive lists) and `codexcli-mcp.ts` refuses instead
|
|
23049
|
+
* (Codex's same-named key means a different thing).
|
|
23050
|
+
*
|
|
23051
|
+
* `disabledTools` has no counterpart upstream (expressing it would need the
|
|
23052
|
+
* server's full tool list), so the target keeps `supportsDisabledTools: false`
|
|
23053
|
+
* and the processor strips it before it reaches here.
|
|
23054
|
+
*
|
|
23055
|
+
* @see https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
|
|
23056
|
+
*/
|
|
23057
|
+
function renameEnabledToolsToTools({ server, serverName, logger }) {
|
|
23058
|
+
const { enabledTools, ...rest } = server;
|
|
23059
|
+
if (enabledTools === void 0) return rest;
|
|
23060
|
+
if (rest.tools !== void 0) logger?.warn(`[CopilotcliMcp] MCP server "${serverName}" declares both 'tools' and 'enabledTools'; keeping 'enabledTools' and dropping 'tools'.`);
|
|
23061
|
+
return {
|
|
23062
|
+
...rest,
|
|
23063
|
+
tools: enabledTools
|
|
23064
|
+
};
|
|
23065
|
+
}
|
|
23066
|
+
/**
|
|
23067
|
+
* Copilot CLI's `tools` accepts more than the canonical `enabledTools` shape:
|
|
23068
|
+
* `--tools` documents `*`, a comma-separated list, or `""`, so a hand-written or
|
|
23069
|
+
* CLI-written entry can be a bare string. Canonical `enabledTools` is
|
|
23070
|
+
* `string[]`, and `RulesyncMcp` throws on a schema failure — carrying a string
|
|
23071
|
+
* through would reject the WHOLE imported MCP file rather than one key. Anything
|
|
23072
|
+
* that is not a string array is therefore left under `tools` untouched, matching
|
|
23073
|
+
* `codexcli-mcp.ts`'s `isValidRenamedArray` guard on the same class of rename.
|
|
23074
|
+
*/
|
|
23075
|
+
function isStringArray(value) {
|
|
23076
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
23077
|
+
}
|
|
23078
|
+
/**
|
|
22827
23079
|
* Resolves and sets the transport type for each MCP server config.
|
|
22828
23080
|
* GitHub Copilot CLI requires the "type" field for each server. A server it
|
|
22829
23081
|
* cannot express — one that names no transport, or names one it carries no way
|
|
@@ -22867,7 +23119,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22867
23119
|
}
|
|
22868
23120
|
const { httpUrl: _httpUrl, ...rest } = parsed;
|
|
22869
23121
|
result[name] = {
|
|
22870
|
-
...
|
|
23122
|
+
...renameEnabledToolsToTools({
|
|
23123
|
+
server: rest,
|
|
23124
|
+
serverName: name,
|
|
23125
|
+
logger
|
|
23126
|
+
}),
|
|
22871
23127
|
type,
|
|
22872
23128
|
url
|
|
22873
23129
|
};
|
|
@@ -22885,7 +23141,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22885
23141
|
continue;
|
|
22886
23142
|
}
|
|
22887
23143
|
result[name] = {
|
|
22888
|
-
...
|
|
23144
|
+
...renameEnabledToolsToTools({
|
|
23145
|
+
server: local,
|
|
23146
|
+
serverName: name,
|
|
23147
|
+
logger
|
|
23148
|
+
}),
|
|
22889
23149
|
type,
|
|
22890
23150
|
command: head,
|
|
22891
23151
|
...tail.length > 0 && { args: tail }
|
|
@@ -22899,11 +23159,19 @@ function addTypeField(mcpServers, logger) {
|
|
|
22899
23159
|
function removeTypeField(config) {
|
|
22900
23160
|
const result = {};
|
|
22901
23161
|
for (const [name, server] of Object.entries(config.mcpServers ?? {})) {
|
|
22902
|
-
|
|
22903
|
-
|
|
23162
|
+
const { tools, ...withoutTools } = server;
|
|
23163
|
+
const restored = isStringArray(tools) ? {
|
|
23164
|
+
...withoutTools,
|
|
23165
|
+
enabledTools: tools
|
|
23166
|
+
} : {
|
|
23167
|
+
...withoutTools,
|
|
23168
|
+
...tools !== void 0 && { tools }
|
|
23169
|
+
};
|
|
23170
|
+
if (restored.type !== "stdio") {
|
|
23171
|
+
result[name] = restored;
|
|
22904
23172
|
continue;
|
|
22905
23173
|
}
|
|
22906
|
-
const { type: _, ...rest } =
|
|
23174
|
+
const { type: _, ...rest } = restored;
|
|
22907
23175
|
result[name] = rest;
|
|
22908
23176
|
}
|
|
22909
23177
|
return result;
|
|
@@ -23164,6 +23432,78 @@ var CursorMcp = class CursorMcp extends ToolMcp {
|
|
|
23164
23432
|
};
|
|
23165
23433
|
//#endregion
|
|
23166
23434
|
//#region src/features/mcp/deepagents-mcp.ts
|
|
23435
|
+
const TOOL_NAME = "deepagents";
|
|
23436
|
+
/**
|
|
23437
|
+
* Map a canonical transport onto the three dcode accepts.
|
|
23438
|
+
*
|
|
23439
|
+
* `_resolve_server_type` takes `stdio`, `sse` and `http`, plus the aliases
|
|
23440
|
+
* `streamable_http` / `streamable-http` → `http`. Rulesync's canonical
|
|
23441
|
+
* vocabulary is wider: `local` and `ws` are spellings dcode rejects outright,
|
|
23442
|
+
* and a rejected server is dropped at load time with only a log line. `local`
|
|
23443
|
+
* has an exact equivalent so it is translated; `ws` has none and is skipped at
|
|
23444
|
+
* generate time instead, where the warning can still reach the author.
|
|
23445
|
+
*
|
|
23446
|
+
* @see https://docs.langchain.com/oss/deepagents/code/mcp-tools
|
|
23447
|
+
*/
|
|
23448
|
+
function normalizeDeepagentsTransport(transport) {
|
|
23449
|
+
switch (transport) {
|
|
23450
|
+
case "local":
|
|
23451
|
+
case "stdio": return "stdio";
|
|
23452
|
+
case "streamable-http":
|
|
23453
|
+
case "streamable_http":
|
|
23454
|
+
case "http": return "http";
|
|
23455
|
+
case "sse": return "sse";
|
|
23456
|
+
default: return;
|
|
23457
|
+
}
|
|
23458
|
+
}
|
|
23459
|
+
/**
|
|
23460
|
+
* Translate one canonical server into dcode's `.mcp.json` shape, or `null` to
|
|
23461
|
+
* skip it.
|
|
23462
|
+
*
|
|
23463
|
+
* Two upstream constraints from `_validate_tool_filter_fields` are enforced
|
|
23464
|
+
* here, because breaking either one makes dcode drop the whole server: the two
|
|
23465
|
+
* filters are mutually exclusive on a single server, and neither may be an
|
|
23466
|
+
* empty list.
|
|
23467
|
+
*/
|
|
23468
|
+
function toDeepagentsServer({ name, server, logger }) {
|
|
23469
|
+
const rawTransport = server.transport ?? server.type;
|
|
23470
|
+
if (rawTransport === "ws") return warnAndSkipMcpServer({
|
|
23471
|
+
toolName: TOOL_NAME,
|
|
23472
|
+
serverName: name,
|
|
23473
|
+
reason: "the WebSocket transport, which deepagents does not support",
|
|
23474
|
+
logger
|
|
23475
|
+
});
|
|
23476
|
+
const { enabledTools, disabledTools, type: _type, transport, ...rest } = server;
|
|
23477
|
+
const converted = { ...rest };
|
|
23478
|
+
const normalized = normalizeDeepagentsTransport(rawTransport);
|
|
23479
|
+
if (normalized !== void 0) if (transport !== void 0) converted.transport = normalized;
|
|
23480
|
+
else converted.type = normalized;
|
|
23481
|
+
if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
|
|
23482
|
+
toolName: TOOL_NAME,
|
|
23483
|
+
serverName: name,
|
|
23484
|
+
reason: "both enabledTools and disabledTools, which deepagents rejects — pick one",
|
|
23485
|
+
logger
|
|
23486
|
+
});
|
|
23487
|
+
if (enabledTools !== void 0) {
|
|
23488
|
+
if (enabledTools.length === 0) return warnAndSkipMcpServer({
|
|
23489
|
+
toolName: TOOL_NAME,
|
|
23490
|
+
serverName: name,
|
|
23491
|
+
reason: "an empty enabledTools list, which allows no tools at all and which deepagents rejects",
|
|
23492
|
+
logger
|
|
23493
|
+
});
|
|
23494
|
+
converted.allowedTools = enabledTools;
|
|
23495
|
+
} else if (disabledTools !== void 0) if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
|
|
23496
|
+
else converted.disabledTools = disabledTools;
|
|
23497
|
+
return converted;
|
|
23498
|
+
}
|
|
23499
|
+
/** Lift dcode's spellings back into the canonical model. */
|
|
23500
|
+
function toRulesyncServer(server) {
|
|
23501
|
+
const { allowedTools, ...rest } = server;
|
|
23502
|
+
const converted = { ...rest };
|
|
23503
|
+
for (const key of ["type", "transport"]) if (converted[key] === "streamable_http" || converted[key] === "streamable-http") converted[key] = "http";
|
|
23504
|
+
if (Array.isArray(allowedTools)) converted.enabledTools = allowedTools;
|
|
23505
|
+
return converted;
|
|
23506
|
+
}
|
|
23167
23507
|
var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
23168
23508
|
json;
|
|
23169
23509
|
constructor(params) {
|
|
@@ -23198,12 +23538,22 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
23198
23538
|
validate
|
|
23199
23539
|
});
|
|
23200
23540
|
}
|
|
23201
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
23541
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
23202
23542
|
const paths = this.getSettablePaths({ global });
|
|
23203
23543
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2);
|
|
23544
|
+
const json = JSON.parse(fileContent);
|
|
23545
|
+
const mcpServers = {};
|
|
23546
|
+
for (const [name, server] of Object.entries(rulesyncMcp.getMcpServers())) {
|
|
23547
|
+
const converted = toDeepagentsServer({
|
|
23548
|
+
name,
|
|
23549
|
+
server,
|
|
23550
|
+
logger
|
|
23551
|
+
});
|
|
23552
|
+
if (converted !== null) mcpServers[name] = converted;
|
|
23553
|
+
}
|
|
23204
23554
|
const mcpJson = {
|
|
23205
|
-
...
|
|
23206
|
-
mcpServers
|
|
23555
|
+
...json,
|
|
23556
|
+
mcpServers
|
|
23207
23557
|
};
|
|
23208
23558
|
return new DeepagentsMcp({
|
|
23209
23559
|
outputRoot,
|
|
@@ -23214,7 +23564,9 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
23214
23564
|
});
|
|
23215
23565
|
}
|
|
23216
23566
|
toRulesyncMcp() {
|
|
23217
|
-
|
|
23567
|
+
const servers = isRecord$1(this.json.mcpServers) ? this.json.mcpServers : {};
|
|
23568
|
+
const mcpServers = Object.fromEntries(Object.entries(servers).map(([name, server]) => [name, isRecord$1(server) ? toRulesyncServer(server) : server]));
|
|
23569
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
23218
23570
|
}
|
|
23219
23571
|
validate() {
|
|
23220
23572
|
return {
|
|
@@ -23471,11 +23823,11 @@ function applyGooseStdioFields(ext, config) {
|
|
|
23471
23823
|
if (Array.isArray(command)) {
|
|
23472
23824
|
if (typeof command[0] === "string") ext.cmd = command[0];
|
|
23473
23825
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23474
|
-
const args = isStringArray$
|
|
23826
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23475
23827
|
if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
|
|
23476
23828
|
} else if (typeof command === "string") {
|
|
23477
23829
|
ext.cmd = command;
|
|
23478
|
-
if (isStringArray$
|
|
23830
|
+
if (isStringArray$2(config.args)) ext.args = config.args;
|
|
23479
23831
|
}
|
|
23480
23832
|
if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
|
|
23481
23833
|
}
|
|
@@ -23577,7 +23929,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
23577
23929
|
else if (type === "streamable_http") server.type = "http";
|
|
23578
23930
|
else server.type = "stdio";
|
|
23579
23931
|
if (typeof ext.cmd === "string") server.command = ext.cmd;
|
|
23580
|
-
if (isStringArray$
|
|
23932
|
+
if (isStringArray$2(ext.args)) server.args = ext.args;
|
|
23581
23933
|
if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
|
|
23582
23934
|
if (typeof ext.uri === "string") server.url = ext.uri;
|
|
23583
23935
|
if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
|
|
@@ -23598,11 +23950,11 @@ function buildGoosePluginStdioServer(config) {
|
|
|
23598
23950
|
if (Array.isArray(command)) {
|
|
23599
23951
|
if (typeof command[0] === "string") server.command = command[0];
|
|
23600
23952
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23601
|
-
const args = isStringArray$
|
|
23953
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23602
23954
|
if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
|
|
23603
23955
|
} else if (typeof command === "string") {
|
|
23604
23956
|
server.command = command;
|
|
23605
|
-
if (isStringArray$
|
|
23957
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
23606
23958
|
}
|
|
23607
23959
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
23608
23960
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -23934,7 +24286,7 @@ function copyHermesOauth(source) {
|
|
|
23934
24286
|
"client_secret"
|
|
23935
24287
|
]) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
23936
24288
|
if (typeof source.redirect_port === "number") oauth.redirect_port = source.redirect_port;
|
|
23937
|
-
if (isStringArray$
|
|
24289
|
+
if (isStringArray$2(source.scopes)) oauth.scopes = source.scopes;
|
|
23938
24290
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
23939
24291
|
}
|
|
23940
24292
|
function copyHermesAdvancedFields(source, target) {
|
|
@@ -23943,7 +24295,7 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
23943
24295
|
target.auth = source.auth;
|
|
23944
24296
|
copied = true;
|
|
23945
24297
|
}
|
|
23946
|
-
if (typeof source.client_cert === "string" || isStringArray$
|
|
24298
|
+
if (typeof source.client_cert === "string" || isStringArray$2(source.client_cert)) {
|
|
23947
24299
|
target.client_cert = source.client_cert;
|
|
23948
24300
|
copied = true;
|
|
23949
24301
|
}
|
|
@@ -24012,8 +24364,8 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
24012
24364
|
*/
|
|
24013
24365
|
function buildHermesToolsBlock(config) {
|
|
24014
24366
|
const tools = {};
|
|
24015
|
-
if (isStringArray$
|
|
24016
|
-
if (isStringArray$
|
|
24367
|
+
if (isStringArray$2(config.enabledTools)) tools.include = config.enabledTools;
|
|
24368
|
+
if (isStringArray$2(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
24017
24369
|
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
24018
24370
|
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
24019
24371
|
return tools;
|
|
@@ -24025,8 +24377,8 @@ function buildHermesToolsBlock(config) {
|
|
|
24025
24377
|
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
24026
24378
|
*/
|
|
24027
24379
|
function applyHermesToolsBlock(hermesTools, server) {
|
|
24028
|
-
if (isStringArray$
|
|
24029
|
-
if (isStringArray$
|
|
24380
|
+
if (isStringArray$2(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
24381
|
+
if (isStringArray$2(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
24030
24382
|
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
24031
24383
|
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
24032
24384
|
}
|
|
@@ -24052,11 +24404,11 @@ function convertServerToHermes(config) {
|
|
|
24052
24404
|
if (Array.isArray(command)) {
|
|
24053
24405
|
if (typeof command[0] === "string") out.command = command[0];
|
|
24054
24406
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
24055
|
-
const args = isStringArray$
|
|
24407
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
24056
24408
|
if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
|
|
24057
24409
|
} else if (typeof command === "string") {
|
|
24058
24410
|
out.command = command;
|
|
24059
|
-
if (isStringArray$
|
|
24411
|
+
if (isStringArray$2(config.args)) out.args = config.args;
|
|
24060
24412
|
}
|
|
24061
24413
|
if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
|
|
24062
24414
|
} else if (url !== void 0) {
|
|
@@ -24106,7 +24458,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
24106
24458
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
24107
24459
|
const server = {};
|
|
24108
24460
|
if (typeof config.command === "string") server.command = config.command;
|
|
24109
|
-
if (isStringArray$
|
|
24461
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
24110
24462
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
24111
24463
|
if (typeof config.url === "string") server.url = config.url;
|
|
24112
24464
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
@@ -25064,13 +25416,17 @@ function mergeToolLists(...lists) {
|
|
|
25064
25416
|
*
|
|
25065
25417
|
* Both native names are documented per-server fields, so a config that already
|
|
25066
25418
|
* spells them natively keeps working: the two lists are merged rather than
|
|
25067
|
-
* one overwriting the other.
|
|
25419
|
+
* one overwriting the other. That is the "merge" branch of the collision rule
|
|
25420
|
+
* documented on `enabledTools` in `src/types/mcp.ts` — it applies here because
|
|
25421
|
+
* the native names are redundant spellings of the same additive lists;
|
|
25422
|
+
* `copilotcli-mcp.ts` lets the canonical value win instead, and
|
|
25423
|
+
* `codexcli-mcp.ts` refuses the canonical value instead.
|
|
25068
25424
|
* @see https://kiro.dev/docs/mcp/configuration/
|
|
25069
25425
|
*/
|
|
25070
25426
|
function toKiroMcpServers(servers) {
|
|
25071
25427
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25072
25428
|
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
25073
|
-
const autoApprove = mergeToolLists(isStringArray$
|
|
25429
|
+
const autoApprove = mergeToolLists(isStringArray$2(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
25074
25430
|
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
25075
25431
|
return [name, {
|
|
25076
25432
|
...rest,
|
|
@@ -25094,7 +25450,7 @@ function fromKiroMcpServers(servers) {
|
|
|
25094
25450
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25095
25451
|
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
25096
25452
|
const { autoApprove, ...rest } = server;
|
|
25097
|
-
if (!isStringArray$
|
|
25453
|
+
if (!isStringArray$2(autoApprove)) return [name, server];
|
|
25098
25454
|
return [name, {
|
|
25099
25455
|
...rest,
|
|
25100
25456
|
kiroAutoApprove: autoApprove
|
|
@@ -26236,7 +26592,7 @@ async function readRovodevConfigYaml({ outputRoot }) {
|
|
|
26236
26592
|
}
|
|
26237
26593
|
function disabledNamesOf(config) {
|
|
26238
26594
|
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
26239
|
-
return isStringArray$
|
|
26595
|
+
return isStringArray$2(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
26240
26596
|
}
|
|
26241
26597
|
/**
|
|
26242
26598
|
* Auxiliary writer for the `mcp:` block of `.rovodev/config.yml` (project) /
|
|
@@ -26369,7 +26725,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
26369
26725
|
return [];
|
|
26370
26726
|
}
|
|
26371
26727
|
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
26372
|
-
const existingDisabled = isStringArray$
|
|
26728
|
+
const existingDisabled = isStringArray$2(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
26373
26729
|
const managedNameSet = new Set(managedNames);
|
|
26374
26730
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
26375
26731
|
if (reEnabled.length > 0) logger?.warn(`Rovo Dev MCP: re-enabling ${reEnabled.join(", ")} — the canonical config does not mark ${reEnabled.length === 1 ? "it" : "them"} disabled. Set "disabled": true in .rulesync/mcp.jsonc to keep a managed server off.`);
|
|
@@ -27211,7 +27567,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27211
27567
|
meta: {
|
|
27212
27568
|
supportsProject: true,
|
|
27213
27569
|
supportsGlobal: true,
|
|
27214
|
-
supportsEnabledTools:
|
|
27570
|
+
supportsEnabledTools: true,
|
|
27215
27571
|
supportsDisabledTools: false
|
|
27216
27572
|
}
|
|
27217
27573
|
}],
|
|
@@ -27229,8 +27585,8 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27229
27585
|
meta: {
|
|
27230
27586
|
supportsProject: true,
|
|
27231
27587
|
supportsGlobal: true,
|
|
27232
|
-
supportsEnabledTools:
|
|
27233
|
-
supportsDisabledTools:
|
|
27588
|
+
supportsEnabledTools: true,
|
|
27589
|
+
supportsDisabledTools: true
|
|
27234
27590
|
}
|
|
27235
27591
|
}],
|
|
27236
27592
|
["factorydroid", {
|
|
@@ -27239,7 +27595,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27239
27595
|
supportsProject: true,
|
|
27240
27596
|
supportsGlobal: true,
|
|
27241
27597
|
supportsEnabledTools: false,
|
|
27242
|
-
supportsDisabledTools:
|
|
27598
|
+
supportsDisabledTools: true
|
|
27243
27599
|
}
|
|
27244
27600
|
}],
|
|
27245
27601
|
["goose", {
|
|
@@ -27365,7 +27721,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27365
27721
|
supportsProject: true,
|
|
27366
27722
|
supportsGlobal: false,
|
|
27367
27723
|
supportsEnabledTools: false,
|
|
27368
|
-
supportsDisabledTools:
|
|
27724
|
+
supportsDisabledTools: true
|
|
27369
27725
|
}
|
|
27370
27726
|
}],
|
|
27371
27727
|
["zoocode", {
|
|
@@ -27374,7 +27730,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27374
27730
|
supportsProject: true,
|
|
27375
27731
|
supportsGlobal: false,
|
|
27376
27732
|
supportsEnabledTools: false,
|
|
27377
|
-
supportsDisabledTools:
|
|
27733
|
+
supportsDisabledTools: true
|
|
27378
27734
|
}
|
|
27379
27735
|
}],
|
|
27380
27736
|
["rovodev", {
|
|
@@ -28035,6 +28391,34 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
28035
28391
|
//#endregion
|
|
28036
28392
|
//#region src/features/permissions/antigravity-cli-permissions.ts
|
|
28037
28393
|
/**
|
|
28394
|
+
* Top-level `~/.gemini/antigravity-cli/settings.json` keys the `antigravity-cli`
|
|
28395
|
+
* permissions override may author. They are siblings of `permissions`, passed
|
|
28396
|
+
* through verbatim in both directions.
|
|
28397
|
+
*/
|
|
28398
|
+
const ANTIGRAVITY_CLI_OVERRIDE_KEYS = [
|
|
28399
|
+
"toolPermission",
|
|
28400
|
+
"enableTerminalSandbox",
|
|
28401
|
+
"artifactReviewPolicy",
|
|
28402
|
+
"allowNonWorkspaceAccess",
|
|
28403
|
+
"agentMode"
|
|
28404
|
+
];
|
|
28405
|
+
const moduleLogger$2 = fallbackLogger;
|
|
28406
|
+
/**
|
|
28407
|
+
* Narrow an imported `settings.json` value to one of the documented values of
|
|
28408
|
+
* an enum-typed override key. The override schema declares `toolPermission`,
|
|
28409
|
+
* `artifactReviewPolicy` and `agentMode` as `z.enum`, so carrying an
|
|
28410
|
+
* unrecognized value through — a hand-written typo, or a preset a newer
|
|
28411
|
+
* Antigravity release added — would fail validation of the whole imported
|
|
28412
|
+
* `.rulesync/permissions.jsonc` instead of dropping just that key. A dropped
|
|
28413
|
+
* value is warned about rather than discarded silently, so a preset rulesync
|
|
28414
|
+
* does not know about yet is visible instead of quietly missing.
|
|
28415
|
+
*/
|
|
28416
|
+
function pickDocumentedValue({ key, value, documented }) {
|
|
28417
|
+
if (typeof value !== "string") return void 0;
|
|
28418
|
+
if (documented.includes(value)) return value;
|
|
28419
|
+
moduleLogger$2.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.`);
|
|
28420
|
+
}
|
|
28421
|
+
/**
|
|
28038
28422
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
28039
28423
|
* Antigravity CLI Fine-Grained Permissions Engine action vocabulary
|
|
28040
28424
|
* (`read_file`, `write_file`, `read_url`, `command`, `mcp`, plus `execute_url` /
|
|
@@ -28109,9 +28493,10 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
28109
28493
|
* file (global scope only). The file holds other CLI settings besides
|
|
28110
28494
|
* permissions, so it is never deleted.
|
|
28111
28495
|
*
|
|
28112
|
-
*
|
|
28496
|
+
* Five CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
28113
28497
|
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
28114
|
-
* `artifactReviewPolicy` and `
|
|
28498
|
+
* `artifactReviewPolicy`, `allowNonWorkspaceAccess` and `agentMode` (the
|
|
28499
|
+
* baseline execution mode) — are authored and
|
|
28115
28500
|
* round-tripped through the `antigravity-cli` permissions override (see
|
|
28116
28501
|
* `AntigravityCliPermissionsOverrideSchema`).
|
|
28117
28502
|
*/
|
|
@@ -28175,10 +28560,10 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28175
28560
|
permissions: mergedPermissions
|
|
28176
28561
|
};
|
|
28177
28562
|
const override = config["antigravity-cli"];
|
|
28178
|
-
|
|
28179
|
-
|
|
28180
|
-
|
|
28181
|
-
|
|
28563
|
+
for (const key of ANTIGRAVITY_CLI_OVERRIDE_KEYS) {
|
|
28564
|
+
const value = override?.[key];
|
|
28565
|
+
if (value !== void 0) merged[key] = value;
|
|
28566
|
+
}
|
|
28182
28567
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
28183
28568
|
return new AntigravityCliPermissions({
|
|
28184
28569
|
outputRoot,
|
|
@@ -28203,10 +28588,26 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28203
28588
|
deny: permissions.deny ?? []
|
|
28204
28589
|
});
|
|
28205
28590
|
const override = {};
|
|
28206
|
-
|
|
28591
|
+
const toolPermission = pickDocumentedValue({
|
|
28592
|
+
key: "toolPermission",
|
|
28593
|
+
value: settings.toolPermission,
|
|
28594
|
+
documented: ANTIGRAVITY_CLI_TOOL_PERMISSIONS
|
|
28595
|
+
});
|
|
28596
|
+
if (toolPermission !== void 0) override.toolPermission = toolPermission;
|
|
28207
28597
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
28208
|
-
|
|
28598
|
+
const artifactReviewPolicy = pickDocumentedValue({
|
|
28599
|
+
key: "artifactReviewPolicy",
|
|
28600
|
+
value: settings.artifactReviewPolicy,
|
|
28601
|
+
documented: ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES
|
|
28602
|
+
});
|
|
28603
|
+
if (artifactReviewPolicy !== void 0) override.artifactReviewPolicy = artifactReviewPolicy;
|
|
28209
28604
|
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
28605
|
+
const agentMode = pickDocumentedValue({
|
|
28606
|
+
key: "agentMode",
|
|
28607
|
+
value: settings.agentMode,
|
|
28608
|
+
documented: ANTIGRAVITY_CLI_AGENT_MODES
|
|
28609
|
+
});
|
|
28610
|
+
if (agentMode !== void 0) override.agentMode = agentMode;
|
|
28210
28611
|
const result = { ...config };
|
|
28211
28612
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
28212
28613
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -29032,6 +29433,119 @@ function deepMergeRecords(base, patch) {
|
|
|
29032
29433
|
}
|
|
29033
29434
|
return merged;
|
|
29034
29435
|
}
|
|
29436
|
+
/**
|
|
29437
|
+
* `sandbox.*` paths Claude Code honors only from user settings, managed settings
|
|
29438
|
+
* and the `--settings` CLI flag. Written into a project `.claude/settings.json`
|
|
29439
|
+
* they are silently ignored, so a committed file would read as though it
|
|
29440
|
+
* enforced a sandbox policy while doing nothing — security-relevant for
|
|
29441
|
+
* `network.strictAllowlist` and the credential-masking keys in particular.
|
|
29442
|
+
* Generation therefore drops them at project scope with a per-key warning and
|
|
29443
|
+
* emits them only under `--global`.
|
|
29444
|
+
*
|
|
29445
|
+
* Deliberately NOT listed:
|
|
29446
|
+
* - `bwrapPath` / `socatPath`: v2.1.232 added them to the managed-settings
|
|
29447
|
+
* approval dialog, which is a consent prompt, not a project-scope rejection.
|
|
29448
|
+
* - `credentials.envVars` / `credentials.files`: the ignored-at-project-scope
|
|
29449
|
+
* unit is the individual entry's mode, not the settings key, and the same
|
|
29450
|
+
* lists carry `deny` entries that project settings *do* honor — dropping a
|
|
29451
|
+
* whole list would remove real restrictions. `stripProjectIgnoredMaskEntries`
|
|
29452
|
+
* filters those lists per entry instead.
|
|
29453
|
+
*
|
|
29454
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
29455
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md — v2.1.232 scoped `sandbox.ripgrep`
|
|
29456
|
+
*/
|
|
29457
|
+
const CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS = [
|
|
29458
|
+
["filesystem", "disabled"],
|
|
29459
|
+
["network", "strictAllowlist"],
|
|
29460
|
+
["network", "tlsTerminate"],
|
|
29461
|
+
["credentials", "allowPlaintextInject"],
|
|
29462
|
+
["credentials", "awsPairs"],
|
|
29463
|
+
["credentials", "sigv4"],
|
|
29464
|
+
["allowAppleEvents"],
|
|
29465
|
+
["ripgrep"]
|
|
29466
|
+
];
|
|
29467
|
+
/**
|
|
29468
|
+
* Copy of the authored `sandbox` override with the user/managed-only paths
|
|
29469
|
+
* removed, warning once per dropped path. Only the override copy is filtered —
|
|
29470
|
+
* a value already hand-written in the target file is left untouched, matching
|
|
29471
|
+
* the `qwencode` `security.allowPrivateNetworkHooks` precedent.
|
|
29472
|
+
*/
|
|
29473
|
+
function stripGlobalOnlySandboxPaths({ sandbox, relativeFilePath, logger }) {
|
|
29474
|
+
const filtered = structuredClone(sandbox);
|
|
29475
|
+
for (const path of CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS) {
|
|
29476
|
+
const leaf = path.at(-1);
|
|
29477
|
+
if (leaf === void 0) continue;
|
|
29478
|
+
const parentPath = path.slice(0, -1);
|
|
29479
|
+
let parent = filtered;
|
|
29480
|
+
for (const segment of parentPath) {
|
|
29481
|
+
const next = parent[segment];
|
|
29482
|
+
if (!isPlainRecord(next)) {
|
|
29483
|
+
parent = {};
|
|
29484
|
+
break;
|
|
29485
|
+
}
|
|
29486
|
+
parent = next;
|
|
29487
|
+
}
|
|
29488
|
+
if (parent[leaf] === void 0) continue;
|
|
29489
|
+
delete parent[leaf];
|
|
29490
|
+
const [container] = parentPath;
|
|
29491
|
+
if (container !== void 0 && isPlainRecord(filtered[container])) {
|
|
29492
|
+
if (Object.keys(filtered[container]).length === 0) delete filtered[container];
|
|
29493
|
+
}
|
|
29494
|
+
logger?.warn(`Claude Code permissions: 'sandbox.${path.join(".")}' is only honored in user/managed/--settings settings, so it is not written to the project-scoped ${relativeFilePath}. Author it in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
29495
|
+
}
|
|
29496
|
+
return filtered;
|
|
29497
|
+
}
|
|
29498
|
+
/** The `sandbox.credentials` lists whose entries accept `"mode": "mask"`. */
|
|
29499
|
+
const CLAUDECODE_MASKABLE_CREDENTIAL_LISTS = ["envVars", "files"];
|
|
29500
|
+
/**
|
|
29501
|
+
* Copy of the authored `sandbox` override with the `credentials.envVars` /
|
|
29502
|
+
* `credentials.files` entries whose `mode` is `"mask"` removed, warning once per
|
|
29503
|
+
* list.
|
|
29504
|
+
*
|
|
29505
|
+
* Masking authorizes the sandbox proxy to send the real credential to the listed
|
|
29506
|
+
* hosts, so Claude Code honors it only from user settings, managed settings and
|
|
29507
|
+
* the `--settings` CLI flag; a `mask` entry in a repository's
|
|
29508
|
+
* `.claude/settings.json` is ignored outright. Keeping it would read as though
|
|
29509
|
+
* the credential were masked while nothing protects it — the one project-scope
|
|
29510
|
+
* gap whose consequence is a live credential leaving the sandbox unmasked.
|
|
29511
|
+
*
|
|
29512
|
+
* Filtered per entry rather than per key, because the same lists carry `deny`
|
|
29513
|
+
* entries that project settings *do* honor. `mode` is matched exactly: an entry
|
|
29514
|
+
* Claude Code cannot read as `mask` is not treated as one either (it degrades
|
|
29515
|
+
* such entries to `deny`), so the "reads as masked but isn't" state this guards
|
|
29516
|
+
* against cannot slip through a differently-spelled value.
|
|
29517
|
+
*
|
|
29518
|
+
* Like `stripGlobalOnlySandboxPaths`, only the override copy is filtered — a
|
|
29519
|
+
* value already in the target file is left untouched, which is why the warning
|
|
29520
|
+
* points at it.
|
|
29521
|
+
*
|
|
29522
|
+
* @see https://code.claude.com/docs/en/sandboxing — "`mask` entries … are all
|
|
29523
|
+
* ignored in a repository's `.claude/settings.json` or
|
|
29524
|
+
* `.claude/settings.local.json`"; the file-entry section states the same
|
|
29525
|
+
* settings-source restriction applies there.
|
|
29526
|
+
*/
|
|
29527
|
+
function stripProjectIgnoredMaskEntries({ sandbox, relativeFilePath, logger }) {
|
|
29528
|
+
const credentials = sandbox.credentials;
|
|
29529
|
+
if (!isPlainRecord(credentials)) return sandbox;
|
|
29530
|
+
const filteredCredentials = { ...credentials };
|
|
29531
|
+
let changed = false;
|
|
29532
|
+
for (const listKey of CLAUDECODE_MASKABLE_CREDENTIAL_LISTS) {
|
|
29533
|
+
const list = filteredCredentials[listKey];
|
|
29534
|
+
if (!Array.isArray(list)) continue;
|
|
29535
|
+
const kept = list.filter((entry) => !(isPlainRecord(entry) && entry.mode === "mask"));
|
|
29536
|
+
if (kept.length === list.length) continue;
|
|
29537
|
+
changed = true;
|
|
29538
|
+
const dropped = list.length - kept.length;
|
|
29539
|
+
logger?.warn(`Claude Code permissions: 'sandbox.credentials.${listKey}' entries with 'mode: "mask"' are only honored in user/managed/--settings settings, so ${dropped} of them ${dropped === 1 ? "was" : "were"} not written to the project-scoped ${relativeFilePath}. Author them in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
29540
|
+
if (kept.length === 0) delete filteredCredentials[listKey];
|
|
29541
|
+
else filteredCredentials[listKey] = kept;
|
|
29542
|
+
}
|
|
29543
|
+
if (!changed) return sandbox;
|
|
29544
|
+
const filtered = { ...sandbox };
|
|
29545
|
+
if (Object.keys(filteredCredentials).length === 0) delete filtered.credentials;
|
|
29546
|
+
else filtered.credentials = filteredCredentials;
|
|
29547
|
+
return filtered;
|
|
29548
|
+
}
|
|
29035
29549
|
const CLAUDE_PATH_RULE_ALIASES = {
|
|
29036
29550
|
Write: "Edit",
|
|
29037
29551
|
NotebookEdit: "Edit",
|
|
@@ -29085,7 +29599,7 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29085
29599
|
validate
|
|
29086
29600
|
});
|
|
29087
29601
|
}
|
|
29088
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
|
|
29602
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
29089
29603
|
const paths = ClaudecodePermissions.getSettablePaths();
|
|
29090
29604
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
29091
29605
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -29109,7 +29623,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29109
29623
|
};
|
|
29110
29624
|
}
|
|
29111
29625
|
const overrideSandbox = config.claudecode?.sandbox;
|
|
29112
|
-
if (isPlainRecord(overrideSandbox))
|
|
29626
|
+
if (isPlainRecord(overrideSandbox)) {
|
|
29627
|
+
const scopedSandbox = global ? overrideSandbox : stripProjectIgnoredMaskEntries({
|
|
29628
|
+
sandbox: stripGlobalOnlySandboxPaths({
|
|
29629
|
+
sandbox: overrideSandbox,
|
|
29630
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29631
|
+
logger
|
|
29632
|
+
}),
|
|
29633
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29634
|
+
logger
|
|
29635
|
+
});
|
|
29636
|
+
if (Object.keys(scopedSandbox).length > 0) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, scopedSandbox);
|
|
29637
|
+
}
|
|
29113
29638
|
const managedToolNames = managedClaudeToolNames(config);
|
|
29114
29639
|
const merged = applyPermissions({
|
|
29115
29640
|
settings,
|
|
@@ -31286,7 +31811,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
31286
31811
|
function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
31287
31812
|
const permission = {};
|
|
31288
31813
|
for (const key of GOOSE_PERMISSION_LIST_KEYS) {
|
|
31289
|
-
const toolNames = isStringArray$
|
|
31814
|
+
const toolNames = isStringArray$2(userPermission[key]) ? userPermission[key] : [];
|
|
31290
31815
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
31291
31816
|
for (const toolName of toolNames) {
|
|
31292
31817
|
const category = GOOSE_TO_RULESYNC_TOOL_NAME[toolName] ?? toolName;
|
|
@@ -31301,6 +31826,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
31301
31826
|
const GROKCLI_UI_KEY = "ui";
|
|
31302
31827
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
31303
31828
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
31829
|
+
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
31304
31830
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
31305
31831
|
const MCP_CANONICAL_PREFIX$1 = "mcp__";
|
|
31306
31832
|
const CATEGORY_TO_GROK_TOOL = {
|
|
@@ -31514,8 +32040,9 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
31514
32040
|
deny: buckets.deny,
|
|
31515
32041
|
ask: buckets.ask
|
|
31516
32042
|
};
|
|
31517
|
-
const
|
|
31518
|
-
|
|
32043
|
+
const existingUi = isRecord$1(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {};
|
|
32044
|
+
const uiPatch = global && existingUi[GROKCLI_PERMISSION_MODE_KEY] !== GROKCLI_AUTO_PERMISSION_MODE ? { [GROKCLI_UI_KEY]: {
|
|
32045
|
+
...existingUi,
|
|
31519
32046
|
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
31520
32047
|
} } : {};
|
|
31521
32048
|
return new GrokcliPermissions({
|
|
@@ -31577,7 +32104,7 @@ const ACTION_RANK = {
|
|
|
31577
32104
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
31578
32105
|
*/
|
|
31579
32106
|
function unmanagedEntries(existingPermission, key) {
|
|
31580
|
-
return (isStringArray$
|
|
32107
|
+
return (isStringArray$2(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
31581
32108
|
}
|
|
31582
32109
|
/**
|
|
31583
32110
|
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
@@ -31620,9 +32147,9 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
31620
32147
|
* described more than once resolves to the strictest action.
|
|
31621
32148
|
*/
|
|
31622
32149
|
function parseGrokPermissionArrays(permission) {
|
|
31623
|
-
const allow = isStringArray$
|
|
31624
|
-
const deny = isStringArray$
|
|
31625
|
-
const ask = isStringArray$
|
|
32150
|
+
const allow = isStringArray$2(permission.allow) ? permission.allow : void 0;
|
|
32151
|
+
const deny = isStringArray$2(permission.deny) ? permission.deny : void 0;
|
|
32152
|
+
const ask = isStringArray$2(permission.ask) ? permission.ask : void 0;
|
|
31626
32153
|
const rules = Array.isArray(permission[GROKCLI_PERMISSION_RULES_KEY]) ? permission[GROKCLI_PERMISSION_RULES_KEY] : void 0;
|
|
31627
32154
|
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
31628
32155
|
const result = {};
|
|
@@ -31804,20 +32331,20 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
31804
32331
|
const permission = clonePermissionBlock(provenance.permission);
|
|
31805
32332
|
reconcileCommandAllowlist({
|
|
31806
32333
|
permission,
|
|
31807
|
-
commandAllowlist: isStringArray$
|
|
32334
|
+
commandAllowlist: isStringArray$2(config.command_allowlist) ? config.command_allowlist : []
|
|
31808
32335
|
});
|
|
31809
32336
|
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
31810
32337
|
reconcileNativeDenies({
|
|
31811
32338
|
permission,
|
|
31812
32339
|
category: "bash",
|
|
31813
|
-
patterns: isStringArray$
|
|
32340
|
+
patterns: isStringArray$2(approvals.deny) ? approvals.deny : []
|
|
31814
32341
|
});
|
|
31815
32342
|
const security = isRecord$1(config.security) ? config.security : {};
|
|
31816
32343
|
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
31817
32344
|
reconcileNativeDenies({
|
|
31818
32345
|
permission,
|
|
31819
32346
|
category: "webfetch",
|
|
31820
|
-
patterns: websiteBlocklist.enabled === true && isStringArray$
|
|
32347
|
+
patterns: websiteBlocklist.enabled === true && isStringArray$2(websiteBlocklist.domains) ? websiteBlocklist.domains : []
|
|
31821
32348
|
});
|
|
31822
32349
|
removeEmptyCategories(permission);
|
|
31823
32350
|
const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
|
|
@@ -33245,6 +33772,115 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
33245
33772
|
}
|
|
33246
33773
|
};
|
|
33247
33774
|
//#endregion
|
|
33775
|
+
//#region src/features/permissions/pi-permissions.ts
|
|
33776
|
+
/** The one `settings.json` key this feature owns. */
|
|
33777
|
+
const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
33778
|
+
/**
|
|
33779
|
+
* Permissions generator for Pi Coding Agent.
|
|
33780
|
+
*
|
|
33781
|
+
* Pi exposes no allow/ask/deny rule surface, so no canonical permission
|
|
33782
|
+
* category maps onto it. Its one repository-syncable tool gate is `defaultTools` —
|
|
33783
|
+
* the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
|
|
33784
|
+
* permissions override.
|
|
33785
|
+
*
|
|
33786
|
+
* Scope semantics are the **opposite** of the union-merge most targets use:
|
|
33787
|
+
* a project `defaultTools` array *replaces* the global array rather than adding
|
|
33788
|
+
* to it, so the two scopes are written independently and never combined.
|
|
33789
|
+
* Project scope writes `.pi/settings.json`; global scope writes
|
|
33790
|
+
* `~/.pi/agent/settings.json`.
|
|
33791
|
+
*
|
|
33792
|
+
* The file is hand-edited and holds many unrelated keys (`theme`,
|
|
33793
|
+
* `defaultModel`, `packages`, `sessionDir`, ...), so writes go through the
|
|
33794
|
+
* shared-config gateway with `defaultTools` as the only owned key, and the file
|
|
33795
|
+
* is never deleted.
|
|
33796
|
+
*
|
|
33797
|
+
* @see https://pi.dev/docs/latest/settings
|
|
33798
|
+
*/
|
|
33799
|
+
var PiPermissions = class PiPermissions extends ToolPermissions {
|
|
33800
|
+
constructor(params) {
|
|
33801
|
+
super({
|
|
33802
|
+
...params,
|
|
33803
|
+
fileContent: params.fileContent ?? "{}"
|
|
33804
|
+
});
|
|
33805
|
+
}
|
|
33806
|
+
/** `settings.json` holds unrelated user settings, so it must not be deleted. */
|
|
33807
|
+
isDeletable() {
|
|
33808
|
+
return false;
|
|
33809
|
+
}
|
|
33810
|
+
static getSettablePaths({ global = false } = {}) {
|
|
33811
|
+
return {
|
|
33812
|
+
relativeDirPath: global ? PI_AGENT_DIR_PATH : ".pi",
|
|
33813
|
+
relativeFilePath: PI_SETTINGS_FILE_NAME
|
|
33814
|
+
};
|
|
33815
|
+
}
|
|
33816
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
33817
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33818
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
33819
|
+
return new PiPermissions({
|
|
33820
|
+
outputRoot,
|
|
33821
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33822
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33823
|
+
fileContent,
|
|
33824
|
+
validate,
|
|
33825
|
+
global
|
|
33826
|
+
});
|
|
33827
|
+
}
|
|
33828
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
33829
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33830
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
33831
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
33832
|
+
const defaultTools = rulesyncPermissions.getJson().pi?.defaultTools;
|
|
33833
|
+
const patch = {};
|
|
33834
|
+
if (defaultTools !== void 0) patch[DEFAULT_TOOLS_KEY] = defaultTools;
|
|
33835
|
+
return new PiPermissions({
|
|
33836
|
+
outputRoot,
|
|
33837
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33838
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33839
|
+
fileContent: applySharedConfigPatch({
|
|
33840
|
+
fileKey: sharedConfigFileKey(paths),
|
|
33841
|
+
feature: "permissions",
|
|
33842
|
+
existingContent,
|
|
33843
|
+
patch,
|
|
33844
|
+
filePath
|
|
33845
|
+
}),
|
|
33846
|
+
validate: true,
|
|
33847
|
+
global
|
|
33848
|
+
});
|
|
33849
|
+
}
|
|
33850
|
+
toRulesyncPermissions() {
|
|
33851
|
+
const defaultTools = this.parseSettings()[DEFAULT_TOOLS_KEY];
|
|
33852
|
+
const config = { permission: {} };
|
|
33853
|
+
if (Array.isArray(defaultTools) && defaultTools.every((tool) => typeof tool === "string")) config.pi = { [DEFAULT_TOOLS_KEY]: defaultTools };
|
|
33854
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
33855
|
+
}
|
|
33856
|
+
parseSettings() {
|
|
33857
|
+
try {
|
|
33858
|
+
return parseSharedConfig({
|
|
33859
|
+
format: "json",
|
|
33860
|
+
fileContent: this.getFileContent()
|
|
33861
|
+
});
|
|
33862
|
+
} catch (error) {
|
|
33863
|
+
throw new Error(`Failed to parse Pi settings in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
33864
|
+
}
|
|
33865
|
+
}
|
|
33866
|
+
validate() {
|
|
33867
|
+
return {
|
|
33868
|
+
success: true,
|
|
33869
|
+
error: null
|
|
33870
|
+
};
|
|
33871
|
+
}
|
|
33872
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
33873
|
+
return new PiPermissions({
|
|
33874
|
+
outputRoot,
|
|
33875
|
+
relativeDirPath,
|
|
33876
|
+
relativeFilePath,
|
|
33877
|
+
fileContent: "{}",
|
|
33878
|
+
validate: false,
|
|
33879
|
+
global
|
|
33880
|
+
});
|
|
33881
|
+
}
|
|
33882
|
+
};
|
|
33883
|
+
//#endregion
|
|
33248
33884
|
//#region src/features/permissions/qwencode-permissions.ts
|
|
33249
33885
|
/**
|
|
33250
33886
|
* Qwen Code uses a settings.json file in `.qwen/` (project) or `~/.qwen/` (global).
|
|
@@ -34308,7 +34944,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
34308
34944
|
permission[category] ??= {};
|
|
34309
34945
|
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
34310
34946
|
}
|
|
34311
|
-
if (isStringArray$
|
|
34947
|
+
if (isStringArray$2(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
34312
34948
|
permission.read ??= {};
|
|
34313
34949
|
permission.read[path] ??= "allow";
|
|
34314
34950
|
}
|
|
@@ -35285,8 +35921,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
35285
35921
|
const executionProfiles = isRecord$1(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
35286
35922
|
const defaultProfile = executionProfiles && isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
35287
35923
|
const config = convertWarpToRulesyncPermissions({
|
|
35288
|
-
allow: defaultProfile ? isStringArray$
|
|
35289
|
-
deny: defaultProfile ? isStringArray$
|
|
35924
|
+
allow: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$2(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
35925
|
+
deny: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$2(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
35290
35926
|
});
|
|
35291
35927
|
const warpOverride = {};
|
|
35292
35928
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
@@ -35998,6 +36634,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
35998
36634
|
supportsImport: true
|
|
35999
36635
|
}
|
|
36000
36636
|
}],
|
|
36637
|
+
["pi", {
|
|
36638
|
+
class: PiPermissions,
|
|
36639
|
+
meta: {
|
|
36640
|
+
supportsProject: true,
|
|
36641
|
+
supportsGlobal: true,
|
|
36642
|
+
supportsImport: true
|
|
36643
|
+
}
|
|
36644
|
+
}],
|
|
36001
36645
|
["qwencode", {
|
|
36002
36646
|
class: QwencodePermissions,
|
|
36003
36647
|
meta: {
|
|
@@ -36800,11 +37444,12 @@ var AmpSkill = class AmpSkill extends ToolSkill {
|
|
|
36800
37444
|
};
|
|
36801
37445
|
}
|
|
36802
37446
|
toRulesyncSkill() {
|
|
36803
|
-
const
|
|
37447
|
+
const { name, description, ...ampSection } = this.getFrontmatter();
|
|
36804
37448
|
const rulesyncFrontmatter = {
|
|
36805
|
-
name
|
|
36806
|
-
description
|
|
36807
|
-
targets: ["*"]
|
|
37449
|
+
name,
|
|
37450
|
+
description,
|
|
37451
|
+
targets: ["*"],
|
|
37452
|
+
...Object.keys(ampSection).length > 0 && { amp: ampSection }
|
|
36808
37453
|
};
|
|
36809
37454
|
return new RulesyncSkill({
|
|
36810
37455
|
outputRoot: this.outputRoot,
|
|
@@ -36821,6 +37466,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
|
|
|
36821
37466
|
const settablePaths = AmpSkill.getSettablePaths({ global });
|
|
36822
37467
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
36823
37468
|
const ampFrontmatter = {
|
|
37469
|
+
...rulesyncFrontmatter.amp,
|
|
36824
37470
|
name: rulesyncFrontmatter.name,
|
|
36825
37471
|
description: rulesyncFrontmatter.description
|
|
36826
37472
|
};
|
|
@@ -38307,6 +38953,7 @@ const CursorSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
38307
38953
|
description: zod_mini.z.string(),
|
|
38308
38954
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
38309
38955
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38956
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38310
38957
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
38311
38958
|
});
|
|
38312
38959
|
/**
|
|
@@ -38361,6 +39008,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38361
39008
|
const cursorSection = {
|
|
38362
39009
|
...frontmatter.paths !== void 0 && { paths: frontmatter.paths },
|
|
38363
39010
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
39011
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
38364
39012
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
38365
39013
|
};
|
|
38366
39014
|
const rulesyncFrontmatter = {
|
|
@@ -38388,11 +39036,16 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38388
39036
|
rootFrontmatter: rulesyncFrontmatter,
|
|
38389
39037
|
section: cursorSection
|
|
38390
39038
|
});
|
|
39039
|
+
const resolvedUserInvocable = resolveUserInvocable({
|
|
39040
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
39041
|
+
section: cursorSection
|
|
39042
|
+
});
|
|
38391
39043
|
const cursorFrontmatter = {
|
|
38392
39044
|
name: rulesyncFrontmatter.name,
|
|
38393
39045
|
description: rulesyncFrontmatter.description,
|
|
38394
39046
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
38395
39047
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
39048
|
+
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
38396
39049
|
...cursorSection?.metadata !== void 0 && { metadata: cursorSection.metadata }
|
|
38397
39050
|
};
|
|
38398
39051
|
return new CursorSkill({
|
|
@@ -39247,6 +39900,47 @@ const JunieSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
39247
39900
|
name: zod_mini.z.string(),
|
|
39248
39901
|
description: zod_mini.z.string()
|
|
39249
39902
|
});
|
|
39903
|
+
/** An ATX markdown heading line (`#` … `######`). */
|
|
39904
|
+
const HEADING_LINE = /^#{1,6}(\s|$)/;
|
|
39905
|
+
/**
|
|
39906
|
+
* Junie's own fallback for a `SKILL.md` with no `description`: "If
|
|
39907
|
+
* `description` is not provided in the frontmatter, Junie CLI extracts the
|
|
39908
|
+
* first paragraph of the body content as the description." Headings do not
|
|
39909
|
+
* count as that paragraph — "If the body is also empty or contains only
|
|
39910
|
+
* headings, the skill will fail to load."
|
|
39911
|
+
*
|
|
39912
|
+
* This is import-only. The canonical `RulesyncSkillFrontmatter` requires a
|
|
39913
|
+
* description, so without the fallback a skill Junie itself loads fine aborts
|
|
39914
|
+
* the whole import; generation keeps emitting an explicit description, which
|
|
39915
|
+
* the same docs recommend.
|
|
39916
|
+
*
|
|
39917
|
+
* Heading lines are therefore skipped rather than taken: a body opening with
|
|
39918
|
+
* `# Skill Name` would otherwise import that title as the description and —
|
|
39919
|
+
* because the next generate writes it out explicitly — replace Junie's own
|
|
39920
|
+
* correct fallback with the wrong value everywhere, canonical config included.
|
|
39921
|
+
*
|
|
39922
|
+
* A paragraph runs to the first blank line or heading, and is collapsed onto
|
|
39923
|
+
* one line because it becomes a YAML frontmatter value. A fenced code block is
|
|
39924
|
+
* not treated specially: it is ordinary content, so a body whose first
|
|
39925
|
+
* paragraph is a fence yields the fence text. Returns an empty string when the
|
|
39926
|
+
* body holds no such paragraph, which the caller turns into a skipped skill.
|
|
39927
|
+
*
|
|
39928
|
+
* @see https://junie.jetbrains.com/docs/agent-skills.html
|
|
39929
|
+
*/
|
|
39930
|
+
function deriveDescriptionFromBody(body) {
|
|
39931
|
+
const paragraph = [];
|
|
39932
|
+
for (const rawLine of body.split(/\r?\n/)) {
|
|
39933
|
+
const line = rawLine.trim();
|
|
39934
|
+
if (paragraph.length === 0) {
|
|
39935
|
+
if (line === "" || HEADING_LINE.test(line)) continue;
|
|
39936
|
+
paragraph.push(line);
|
|
39937
|
+
continue;
|
|
39938
|
+
}
|
|
39939
|
+
if (line === "" || HEADING_LINE.test(line)) break;
|
|
39940
|
+
paragraph.push(line);
|
|
39941
|
+
}
|
|
39942
|
+
return paragraph.join(" ").trim();
|
|
39943
|
+
}
|
|
39250
39944
|
/**
|
|
39251
39945
|
* Represents a JetBrains Junie skill directory.
|
|
39252
39946
|
* Skills are stored under the .junie/skills directory with SKILL.md files.
|
|
@@ -39343,7 +40037,16 @@ var JunieSkill = class JunieSkill extends ToolSkill {
|
|
|
39343
40037
|
...params,
|
|
39344
40038
|
getSettablePaths: JunieSkill.getSettablePaths
|
|
39345
40039
|
});
|
|
39346
|
-
|
|
40040
|
+
let frontmatter = loaded.frontmatter;
|
|
40041
|
+
if (isRecord$1(frontmatter) && frontmatter.description === void 0) {
|
|
40042
|
+
const derived = deriveDescriptionFromBody(loaded.body);
|
|
40043
|
+
if (derived === "") throw new Error(`Cannot import ${(0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME)}: it has no description and its body has no paragraph to derive one from, so Junie cannot load it either. Add a description to the frontmatter.`);
|
|
40044
|
+
frontmatter = {
|
|
40045
|
+
...frontmatter,
|
|
40046
|
+
description: derived
|
|
40047
|
+
};
|
|
40048
|
+
}
|
|
40049
|
+
const result = JunieSkillFrontmatterSchema.safeParse(frontmatter);
|
|
39347
40050
|
if (!result.success) {
|
|
39348
40051
|
const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
39349
40052
|
throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
@@ -41808,7 +42511,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
41808
42511
|
meta: {
|
|
41809
42512
|
supportsProject: true,
|
|
41810
42513
|
supportsSimulated: false,
|
|
41811
|
-
supportsGlobal: true
|
|
42514
|
+
supportsGlobal: true,
|
|
42515
|
+
lenientImport: true
|
|
41812
42516
|
}
|
|
41813
42517
|
}],
|
|
41814
42518
|
["kilo", {
|
|
@@ -46344,6 +47048,23 @@ var TaktSubagent = class TaktSubagent extends ToolSubagent {
|
|
|
46344
47048
|
};
|
|
46345
47049
|
//#endregion
|
|
46346
47050
|
//#region src/features/subagents/vibe-subagent.ts
|
|
47051
|
+
/**
|
|
47052
|
+
* Where Vibe reads a custom system prompt from: `.vibe/prompts/<id>.md`
|
|
47053
|
+
* (project) and `~/.vibe/prompts/<id>.md` (user), with the custom directories
|
|
47054
|
+
* winning over the builtins. The id must be a bare file name with no path
|
|
47055
|
+
* separators.
|
|
47056
|
+
* @see vibe/core/prompts/__init__.py
|
|
47057
|
+
*/
|
|
47058
|
+
const VIBE_PROMPTS_DIR_PATH = (0, node_path.join)(".vibe", "prompts");
|
|
47059
|
+
/**
|
|
47060
|
+
* Vibe resolves a `system_prompt_id` by file name, and
|
|
47061
|
+
* `VibeConfigSchema._check_system_prompt` evaluates it during validation — an
|
|
47062
|
+
* unresolvable id raises and the agent is dropped at discovery. The id must
|
|
47063
|
+
* therefore stay a bare slug with no path separators.
|
|
47064
|
+
*/
|
|
47065
|
+
function promptSlug(relativeFilePath) {
|
|
47066
|
+
return (0, node_path.basename)(relativeFilePath).replace(/\.(md|toml)$/, "").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
47067
|
+
}
|
|
46347
47068
|
const VibeSubagentTomlSchema = zod_mini.z.looseObject({
|
|
46348
47069
|
agent_type: zod_mini.z.enum(["agent", "subagent"]),
|
|
46349
47070
|
display_name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -46360,14 +47081,22 @@ const VibeSubagentTomlSchema = zod_mini.z.looseObject({
|
|
|
46360
47081
|
});
|
|
46361
47082
|
var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
46362
47083
|
body;
|
|
46363
|
-
|
|
46364
|
-
|
|
47084
|
+
promptFile;
|
|
47085
|
+
resolvedSystemPrompt;
|
|
47086
|
+
constructor({ body, promptFile = false, resolvedSystemPrompt, ...rest }) {
|
|
47087
|
+
if (rest.validate !== false && !promptFile) try {
|
|
46365
47088
|
VibeSubagentTomlSchema.parse(smol_toml.parse(body));
|
|
46366
47089
|
} catch (error) {
|
|
46367
47090
|
throw new Error(`Invalid TOML in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
46368
47091
|
}
|
|
46369
47092
|
super({ ...rest });
|
|
46370
47093
|
this.body = body;
|
|
47094
|
+
this.promptFile = promptFile;
|
|
47095
|
+
this.resolvedSystemPrompt = resolvedSystemPrompt;
|
|
47096
|
+
}
|
|
47097
|
+
/** True for the companion `.vibe/prompts/<id>.md` file. */
|
|
47098
|
+
isPromptFile() {
|
|
47099
|
+
return this.promptFile;
|
|
46371
47100
|
}
|
|
46372
47101
|
static getSettablePaths(_options = {}) {
|
|
46373
47102
|
return { relativeDirPath: (0, node_path.join)(".vibe", "agents") };
|
|
@@ -46382,7 +47111,9 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46382
47111
|
} catch (error) {
|
|
46383
47112
|
throw new Error(`Failed to parse TOML in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
46384
47113
|
}
|
|
46385
|
-
const { system_prompt, description, display_name, ...vibeSection } = parsed;
|
|
47114
|
+
const { system_prompt, system_prompt_id, description, display_name, ...vibeSection } = parsed;
|
|
47115
|
+
const resolvedBody = this.resolvedSystemPrompt ?? system_prompt;
|
|
47116
|
+
const unresolvedPromptId = this.resolvedSystemPrompt === void 0 ? system_prompt_id : void 0;
|
|
46386
47117
|
const fileStem = (0, node_path.basename)(this.getRelativeFilePath(), ".toml");
|
|
46387
47118
|
const rulesyncFrontmatter = {
|
|
46388
47119
|
targets: ["vibe"],
|
|
@@ -46391,13 +47122,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46391
47122
|
vibe: {
|
|
46392
47123
|
...display_name !== void 0 && { display_name },
|
|
46393
47124
|
...description !== void 0 && { description },
|
|
47125
|
+
...unresolvedPromptId !== void 0 && { system_prompt_id: unresolvedPromptId },
|
|
46394
47126
|
...vibeSection
|
|
46395
47127
|
}
|
|
46396
47128
|
};
|
|
46397
47129
|
return new RulesyncSubagent({
|
|
46398
47130
|
outputRoot: this.outputRoot,
|
|
46399
47131
|
frontmatter: rulesyncFrontmatter,
|
|
46400
|
-
body:
|
|
47132
|
+
body: resolvedBody ?? "",
|
|
46401
47133
|
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
46402
47134
|
relativeFilePath: this.getRelativeFilePath().replace(/\.toml$/, ".md"),
|
|
46403
47135
|
validate: true
|
|
@@ -46412,12 +47144,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46412
47144
|
"description",
|
|
46413
47145
|
"system_prompt"
|
|
46414
47146
|
]);
|
|
47147
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47148
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
46415
47149
|
const tomlObj = {
|
|
46416
47150
|
agent_type: rawSection.agent_type === "agent" ? "agent" : "subagent",
|
|
46417
47151
|
display_name: typeof rawSection.display_name === "string" ? rawSection.display_name : frontmatter.name,
|
|
46418
47152
|
...frontmatter.description ? { description: frontmatter.description } : {},
|
|
46419
|
-
...
|
|
46420
|
-
...
|
|
47153
|
+
...vibeSection,
|
|
47154
|
+
...promptBody ? { system_prompt_id: slug } : {}
|
|
46421
47155
|
};
|
|
46422
47156
|
const body = smol_toml.stringify(tomlObj);
|
|
46423
47157
|
const paths = this.getSettablePaths({ global });
|
|
@@ -46432,6 +47166,36 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46432
47166
|
global
|
|
46433
47167
|
});
|
|
46434
47168
|
}
|
|
47169
|
+
/**
|
|
47170
|
+
* Emits the agent TOML plus its companion prompt file. The prompt file MUST
|
|
47171
|
+
* be written together with the id: `VibeConfigSchema._check_system_prompt`
|
|
47172
|
+
* evaluates the property during validation, so an unresolvable
|
|
47173
|
+
* `system_prompt_id` raises and `AgentRegistry._try_load` drops the agent
|
|
47174
|
+
* with a warning.
|
|
47175
|
+
*/
|
|
47176
|
+
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false }) {
|
|
47177
|
+
return rulesyncSubagents.flatMap((rulesyncSubagent) => {
|
|
47178
|
+
const agentFile = VibeSubagent.fromRulesyncSubagent({
|
|
47179
|
+
outputRoot,
|
|
47180
|
+
relativeDirPath: VibeSubagent.getSettablePaths({ global }).relativeDirPath,
|
|
47181
|
+
rulesyncSubagent,
|
|
47182
|
+
global
|
|
47183
|
+
});
|
|
47184
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47185
|
+
if (!promptBody) return [agentFile];
|
|
47186
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
47187
|
+
return [agentFile, new VibeSubagent({
|
|
47188
|
+
outputRoot,
|
|
47189
|
+
body: promptBody,
|
|
47190
|
+
relativeDirPath: VIBE_PROMPTS_DIR_PATH,
|
|
47191
|
+
relativeFilePath: `${slug}.md`,
|
|
47192
|
+
fileContent: promptBody,
|
|
47193
|
+
promptFile: true,
|
|
47194
|
+
validate: false,
|
|
47195
|
+
global
|
|
47196
|
+
})];
|
|
47197
|
+
});
|
|
47198
|
+
}
|
|
46435
47199
|
validate() {
|
|
46436
47200
|
try {
|
|
46437
47201
|
VibeSubagentTomlSchema.parse(smol_toml.parse(this.body));
|
|
@@ -46456,6 +47220,10 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46456
47220
|
const paths = this.getSettablePaths({ global });
|
|
46457
47221
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
46458
47222
|
const fileContent = await readFileContent(filePath);
|
|
47223
|
+
const resolvedSystemPrompt = await VibeSubagent.readSystemPrompt({
|
|
47224
|
+
outputRoot,
|
|
47225
|
+
fileContent
|
|
47226
|
+
});
|
|
46459
47227
|
const subagent = new VibeSubagent({
|
|
46460
47228
|
outputRoot,
|
|
46461
47229
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -46463,7 +47231,8 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46463
47231
|
body: fileContent.trim(),
|
|
46464
47232
|
fileContent,
|
|
46465
47233
|
validate,
|
|
46466
|
-
global
|
|
47234
|
+
global,
|
|
47235
|
+
...resolvedSystemPrompt !== void 0 && { resolvedSystemPrompt }
|
|
46467
47236
|
});
|
|
46468
47237
|
if (validate) {
|
|
46469
47238
|
const result = subagent.validate();
|
|
@@ -46471,6 +47240,21 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46471
47240
|
}
|
|
46472
47241
|
return subagent;
|
|
46473
47242
|
}
|
|
47243
|
+
/**
|
|
47244
|
+
* Read `.vibe/prompts/<id>.md` for an agent that names a `system_prompt_id`.
|
|
47245
|
+
* Returns `undefined` when there is no id or the file is missing, in which
|
|
47246
|
+
* case import falls back to the legacy `system_prompt` key.
|
|
47247
|
+
*/
|
|
47248
|
+
static async readSystemPrompt({ outputRoot, fileContent }) {
|
|
47249
|
+
let promptId;
|
|
47250
|
+
try {
|
|
47251
|
+
promptId = VibeSubagentTomlSchema.parse(smol_toml.parse(fileContent)).system_prompt_id;
|
|
47252
|
+
} catch {
|
|
47253
|
+
return;
|
|
47254
|
+
}
|
|
47255
|
+
if (typeof promptId !== "string" || promptId === "" || /[\\/]/.test(promptId)) return;
|
|
47256
|
+
return await readFileContentOrNull((0, node_path.join)(outputRoot, VIBE_PROMPTS_DIR_PATH, `${promptId}.md`)) ?? void 0;
|
|
47257
|
+
}
|
|
46474
47258
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
46475
47259
|
return new VibeSubagent({
|
|
46476
47260
|
outputRoot,
|
|
@@ -46513,7 +47297,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46513
47297
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
46514
47298
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
46515
47299
|
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
46516
|
-
if (isStringArray$
|
|
47300
|
+
if (isStringArray$2(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
46517
47301
|
return mode;
|
|
46518
47302
|
}
|
|
46519
47303
|
toRulesyncSubagents() {
|
|
@@ -46525,7 +47309,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46525
47309
|
targets: ["zoocode"],
|
|
46526
47310
|
roo: restRooSection
|
|
46527
47311
|
};
|
|
46528
|
-
if (isStringArray$
|
|
47312
|
+
if (isStringArray$2(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
46529
47313
|
return new RulesyncSubagent({
|
|
46530
47314
|
outputRoot: ".",
|
|
46531
47315
|
frontmatter: rebuilt,
|
|
@@ -49428,8 +50212,13 @@ const STRATEGIES = [
|
|
|
49428
50212
|
* file per rule under `.devin/rules/*.md` (Devin Desktop Cascade) with YAML
|
|
49429
50213
|
* frontmatter carrying a `trigger` (always_on | glob | manual | model_decision)
|
|
49430
50214
|
* plus companion `globs`/`description` fields.
|
|
49431
|
-
* - Global scope:
|
|
49432
|
-
*
|
|
50215
|
+
* - Global scope: mirrors the project layout. The root rule is a single
|
|
50216
|
+
* plain-markdown, always-on file (no frontmatter) at
|
|
50217
|
+
* `~/.config/devin/AGENTS.md`; non-root rules are one file per rule under
|
|
50218
|
+
* `~/.devin/rules/*.md` with the same `trigger`/`globs` frontmatter as project
|
|
50219
|
+
* scope. Note the directory split — the per-rule global directory is the home
|
|
50220
|
+
* `~/.devin/`, not the `~/.config/devin/` tree every other Devin global
|
|
50221
|
+
* surface uses. That is what the rules page documents.
|
|
49433
50222
|
*
|
|
49434
50223
|
* Trigger inference (when no explicit devin trigger is persisted):
|
|
49435
50224
|
* - Specific globs (non wildcard) → glob
|
|
@@ -49460,19 +50249,32 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49460
50249
|
relativeFilePath: DEVIN_GLOBAL_AGENTS_FILE_NAME
|
|
49461
50250
|
};
|
|
49462
50251
|
}
|
|
50252
|
+
/**
|
|
50253
|
+
* The always-on root rule file for a scope: the project-root `AGENTS.md`
|
|
50254
|
+
* Devin CLI / Devin Local reads, or `~/.config/devin/AGENTS.md` in global
|
|
50255
|
+
* scope. Single-sourced so `getSettablePaths`, `fromFile` and
|
|
50256
|
+
* `fromRulesyncRule` cannot disagree about it.
|
|
50257
|
+
*/
|
|
50258
|
+
static getRootPath(global, excludeToolDir) {
|
|
50259
|
+
return global ? DevinRule.getGlobalRootPath(excludeToolDir) : {
|
|
50260
|
+
relativeDirPath: ".",
|
|
50261
|
+
relativeFilePath: "AGENTS.md"
|
|
50262
|
+
};
|
|
50263
|
+
}
|
|
49463
50264
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
49464
|
-
if (global) return {
|
|
50265
|
+
if (global) return {
|
|
50266
|
+
root: DevinRule.getRootPath(true, excludeToolDir),
|
|
50267
|
+
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
50268
|
+
};
|
|
49465
50269
|
return {
|
|
49466
|
-
root:
|
|
49467
|
-
relativeDirPath: ".",
|
|
49468
|
-
relativeFilePath: "AGENTS.md"
|
|
49469
|
-
},
|
|
50270
|
+
root: DevinRule.getRootPath(false, excludeToolDir),
|
|
49470
50271
|
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
49471
50272
|
};
|
|
49472
50273
|
}
|
|
49473
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
49474
|
-
|
|
49475
|
-
|
|
50274
|
+
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
50275
|
+
const nonRootDirPath = buildToolPath(DEVIN_DIR, "rules");
|
|
50276
|
+
if (relativeDirPath !== void 0 ? relativeDirPath !== nonRootDirPath : relativeFilePath === DevinRule.getRootPath(global).relativeFilePath) {
|
|
50277
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49476
50278
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootPath.relativeDirPath, rootPath.relativeFilePath));
|
|
49477
50279
|
return new DevinRule({
|
|
49478
50280
|
outputRoot,
|
|
@@ -49484,19 +50286,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49484
50286
|
root: true
|
|
49485
50287
|
});
|
|
49486
50288
|
}
|
|
49487
|
-
if (relativeFilePath === "AGENTS.md") {
|
|
49488
|
-
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeFilePath));
|
|
49489
|
-
return new DevinRule({
|
|
49490
|
-
outputRoot,
|
|
49491
|
-
relativeDirPath: ".",
|
|
49492
|
-
relativeFilePath: "AGENTS.md",
|
|
49493
|
-
frontmatter: {},
|
|
49494
|
-
body: fileContent,
|
|
49495
|
-
validate,
|
|
49496
|
-
root: true
|
|
49497
|
-
});
|
|
49498
|
-
}
|
|
49499
|
-
const nonRootDirPath = buildToolPath(DEVIN_DIR, "rules");
|
|
49500
50289
|
const filePath = (0, node_path.join)(outputRoot, nonRootDirPath, relativeFilePath);
|
|
49501
50290
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
49502
50291
|
let parsedFrontmatter;
|
|
@@ -49516,8 +50305,9 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49516
50305
|
});
|
|
49517
50306
|
}
|
|
49518
50307
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
49519
|
-
|
|
49520
|
-
|
|
50308
|
+
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
50309
|
+
if (rulesyncFrontmatter.root) {
|
|
50310
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49521
50311
|
return new DevinRule({
|
|
49522
50312
|
outputRoot,
|
|
49523
50313
|
relativeDirPath: rootPath.relativeDirPath,
|
|
@@ -49528,16 +50318,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49528
50318
|
root: true
|
|
49529
50319
|
});
|
|
49530
50320
|
}
|
|
49531
|
-
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
49532
|
-
if (rulesyncFrontmatter.root) return new DevinRule({
|
|
49533
|
-
outputRoot,
|
|
49534
|
-
relativeDirPath: ".",
|
|
49535
|
-
relativeFilePath: "AGENTS.md",
|
|
49536
|
-
frontmatter: {},
|
|
49537
|
-
body: rulesyncRule.getBody(),
|
|
49538
|
-
validate,
|
|
49539
|
-
root: true
|
|
49540
|
-
});
|
|
49541
50321
|
const storedDevin = rulesyncFrontmatter.devin;
|
|
49542
50322
|
const normalized = normalizeStoredDevin(storedDevin);
|
|
49543
50323
|
const storedTrigger = storedDevin?.trigger;
|
|
@@ -49597,7 +50377,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49597
50377
|
error: null
|
|
49598
50378
|
};
|
|
49599
50379
|
}
|
|
49600
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath
|
|
50380
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
49601
50381
|
return new DevinRule({
|
|
49602
50382
|
outputRoot,
|
|
49603
50383
|
relativeDirPath,
|
|
@@ -49605,7 +50385,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49605
50385
|
frontmatter: {},
|
|
49606
50386
|
body: "",
|
|
49607
50387
|
validate: false,
|
|
49608
|
-
root:
|
|
50388
|
+
root: relativeDirPath !== buildToolPath(DEVIN_DIR, "rules")
|
|
49609
50389
|
});
|
|
49610
50390
|
}
|
|
49611
50391
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -50238,6 +51018,11 @@ var KimiCodeRule = class KimiCodeRule extends ToolRule {
|
|
|
50238
51018
|
};
|
|
50239
51019
|
//#endregion
|
|
50240
51020
|
//#region src/features/rules/kiro-rule.ts
|
|
51021
|
+
/**
|
|
51022
|
+
* Kiro loads `AGENTS.md` as steering context from any directory in the
|
|
51023
|
+
* workspace tree (CLI 2.18.0 / IDE 1.0.309), not just the workspace root.
|
|
51024
|
+
*/
|
|
51025
|
+
const KIRO_NESTED_STEERING_FILE_NAME = "AGENTS.md";
|
|
50241
51026
|
const WILDCARD_GLOBS = /* @__PURE__ */ new Set([
|
|
50242
51027
|
"**/*",
|
|
50243
51028
|
"**",
|
|
@@ -50311,8 +51096,17 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50311
51096
|
};
|
|
50312
51097
|
return { nonRoot: { relativeDirPath: steeringDirPath } };
|
|
50313
51098
|
}
|
|
50314
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
51099
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
50315
51100
|
const paths = this.getSettablePaths({ global });
|
|
51101
|
+
const steeringDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
51102
|
+
if (overrideDirPath !== void 0 && overrideDirPath !== steeringDirPath && overrideDirPath !== "." && relativeFilePath === KIRO_NESTED_STEERING_FILE_NAME) return new KiroRule({
|
|
51103
|
+
outputRoot,
|
|
51104
|
+
relativeDirPath: overrideDirPath,
|
|
51105
|
+
relativeFilePath,
|
|
51106
|
+
fileContent: await readFileContent((0, node_path.join)(outputRoot, overrideDirPath, relativeFilePath)),
|
|
51107
|
+
validate,
|
|
51108
|
+
root: false
|
|
51109
|
+
});
|
|
50316
51110
|
const isRoot = "root" in paths && relativeFilePath === paths.root.relativeFilePath;
|
|
50317
51111
|
const relativeDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
50318
51112
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
@@ -50336,6 +51130,12 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50336
51130
|
});
|
|
50337
51131
|
if (params.root) return new KiroRule(params);
|
|
50338
51132
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
51133
|
+
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
51134
|
+
if (!global && subprojectPath) return new KiroRule({
|
|
51135
|
+
...params,
|
|
51136
|
+
relativeDirPath: (0, node_path.join)(subprojectPath),
|
|
51137
|
+
relativeFilePath: KIRO_NESTED_STEERING_FILE_NAME
|
|
51138
|
+
});
|
|
50339
51139
|
const inclusion = deriveKiroInclusion({
|
|
50340
51140
|
kiro: frontmatter.kiro,
|
|
50341
51141
|
globs: frontmatter.globs
|
|
@@ -50346,7 +51146,34 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50346
51146
|
fileContent: stringifyFrontmatter(rulesyncRule.getBody(), inclusion)
|
|
50347
51147
|
});
|
|
50348
51148
|
}
|
|
51149
|
+
/**
|
|
51150
|
+
* The subproject directory a nested `AGENTS.md` scopes, or `undefined` for the
|
|
51151
|
+
* root rule and for the `.kiro/steering/` files.
|
|
51152
|
+
*/
|
|
51153
|
+
getSubprojectPath() {
|
|
51154
|
+
if (this.isRoot() || this.getRelativeFilePath() !== KIRO_NESTED_STEERING_FILE_NAME) return;
|
|
51155
|
+
const relativeDirPath = toPosixPath(this.getRelativeDirPath());
|
|
51156
|
+
if (relativeDirPath === "." || relativeDirPath === "" || relativeDirPath.startsWith(".")) return;
|
|
51157
|
+
return relativeDirPath;
|
|
51158
|
+
}
|
|
50349
51159
|
toRulesyncRule() {
|
|
51160
|
+
const subprojectPath = this.getSubprojectPath();
|
|
51161
|
+
if (subprojectPath !== void 0) {
|
|
51162
|
+
const slug = subprojectPath.replaceAll("/", "-");
|
|
51163
|
+
return new RulesyncRule({
|
|
51164
|
+
outputRoot: this.getOutputRoot(),
|
|
51165
|
+
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
51166
|
+
relativeFilePath: `${slug}-kiro.md`,
|
|
51167
|
+
frontmatter: {
|
|
51168
|
+
targets: ["kiro"],
|
|
51169
|
+
root: false,
|
|
51170
|
+
globs: [`${subprojectPath}/**/*`],
|
|
51171
|
+
agentsmd: { subprojectPath }
|
|
51172
|
+
},
|
|
51173
|
+
body: this.getFileContent(),
|
|
51174
|
+
validate: true
|
|
51175
|
+
});
|
|
51176
|
+
}
|
|
50350
51177
|
const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getRelativeFilePath());
|
|
50351
51178
|
const inclusion = typeof frontmatter.inclusion === "string" ? frontmatter.inclusion : void 0;
|
|
50352
51179
|
if (!inclusion) return this.toRulesyncRuleDefault();
|
|
@@ -50379,6 +51206,26 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50379
51206
|
error: null
|
|
50380
51207
|
};
|
|
50381
51208
|
}
|
|
51209
|
+
/**
|
|
51210
|
+
* Nested `AGENTS.md` files anywhere in the workspace tree, which Kiro loads as
|
|
51211
|
+
* steering context alongside `.kiro/steering/`. Import-only, like every other
|
|
51212
|
+
* nested scan: these are hand-authored files outside a rulesync-owned
|
|
51213
|
+
* directory, so enumerating them for `--delete` would sweep away work rulesync
|
|
51214
|
+
* never wrote.
|
|
51215
|
+
* @see https://kiro.dev/docs/steering/
|
|
51216
|
+
*/
|
|
51217
|
+
static getNestedFilePatterns({ outputRoot }) {
|
|
51218
|
+
const root = toPosixPath(outputRoot);
|
|
51219
|
+
return {
|
|
51220
|
+
include: [`${root}/**/${KIRO_NESTED_STEERING_FILE_NAME}`],
|
|
51221
|
+
ignore: [
|
|
51222
|
+
`${root}/${KIRO_NESTED_STEERING_FILE_NAME}`,
|
|
51223
|
+
`${root}/**/.*/**`,
|
|
51224
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
51225
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
51226
|
+
]
|
|
51227
|
+
};
|
|
51228
|
+
}
|
|
50382
51229
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
50383
51230
|
return new KiroRule({
|
|
50384
51231
|
outputRoot,
|
|
@@ -51847,6 +52694,18 @@ const resolveIncludeLocalRoot = (options) => {
|
|
|
51847
52694
|
return parsed.data.includeLocalRoot ?? true;
|
|
51848
52695
|
};
|
|
51849
52696
|
/**
|
|
52697
|
+
* Whether `candidate` is `base` itself or a class that extends it.
|
|
52698
|
+
*
|
|
52699
|
+
* Class-identity dispatch (`factory.class === Base`) is subclass-blind: a target
|
|
52700
|
+
* whose adapter merely narrows another one — `ZoocodeRule extends RooRule` — fails
|
|
52701
|
+
* a strict `===` and silently falls through. That is how `zoocode` ended up
|
|
52702
|
+
* declaring `localRootMode: "separate-local-file"` while never emitting
|
|
52703
|
+
* `AGENTS.local.md`, in both directions and without a warning.
|
|
52704
|
+
*/
|
|
52705
|
+
function isClassOrSubclassOf({ candidate, base }) {
|
|
52706
|
+
return candidate === base || Object.prototype.isPrototypeOf.call(base, candidate);
|
|
52707
|
+
}
|
|
52708
|
+
/**
|
|
51850
52709
|
* Factory Map mapping tool targets to their rule factories.
|
|
51851
52710
|
* Using Map to preserve insertion order for consistent iteration.
|
|
51852
52711
|
*/
|
|
@@ -52200,7 +53059,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
52200
53059
|
meta: {
|
|
52201
53060
|
extension: "md",
|
|
52202
53061
|
supportsGlobal: true,
|
|
52203
|
-
ruleDiscoveryMode: "auto"
|
|
53062
|
+
ruleDiscoveryMode: "auto",
|
|
53063
|
+
localRootMode: "separate-local-file",
|
|
53064
|
+
localRootFileName: "AGENTS.local.md"
|
|
52204
53065
|
}
|
|
52205
53066
|
}],
|
|
52206
53067
|
["zed", {
|
|
@@ -52487,7 +53348,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52487
53348
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
52488
53349
|
}
|
|
52489
53350
|
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
52490
|
-
if (
|
|
53351
|
+
if (isClassOrSubclassOf({
|
|
53352
|
+
candidate: factory.class,
|
|
53353
|
+
base: ClaudecodeRule
|
|
53354
|
+
})) {
|
|
52491
53355
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
52492
53356
|
return new ClaudecodeRule({
|
|
52493
53357
|
outputRoot: this.outputRoot,
|
|
@@ -52500,7 +53364,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52500
53364
|
localRoot
|
|
52501
53365
|
});
|
|
52502
53366
|
}
|
|
52503
|
-
if (
|
|
53367
|
+
if (isClassOrSubclassOf({
|
|
53368
|
+
candidate: factory.class,
|
|
53369
|
+
base: ClaudecodeLegacyRule
|
|
53370
|
+
})) {
|
|
52504
53371
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
52505
53372
|
return new ClaudecodeLegacyRule({
|
|
52506
53373
|
outputRoot: this.outputRoot,
|
|
@@ -52512,7 +53379,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52512
53379
|
localRoot
|
|
52513
53380
|
});
|
|
52514
53381
|
}
|
|
52515
|
-
if (
|
|
53382
|
+
if (isClassOrSubclassOf({
|
|
53383
|
+
candidate: factory.class,
|
|
53384
|
+
base: RovodevRule
|
|
53385
|
+
})) return new RovodevRule({
|
|
52516
53386
|
outputRoot: this.outputRoot,
|
|
52517
53387
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52518
53388
|
relativeFilePath: fileName,
|
|
@@ -52521,7 +53391,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52521
53391
|
root: true,
|
|
52522
53392
|
localRoot
|
|
52523
53393
|
});
|
|
52524
|
-
if (
|
|
53394
|
+
if (isClassOrSubclassOf({
|
|
53395
|
+
candidate: factory.class,
|
|
53396
|
+
base: RooRule
|
|
53397
|
+
})) return new RooRule({
|
|
52525
53398
|
outputRoot: this.outputRoot,
|
|
52526
53399
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52527
53400
|
relativeFilePath: fileName,
|
|
@@ -52530,7 +53403,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52530
53403
|
root: true,
|
|
52531
53404
|
localRoot
|
|
52532
53405
|
});
|
|
52533
|
-
if (
|
|
53406
|
+
if (isClassOrSubclassOf({
|
|
53407
|
+
candidate: factory.class,
|
|
53408
|
+
base: DevinRule
|
|
53409
|
+
})) return new DevinRule({
|
|
53410
|
+
outputRoot: this.outputRoot,
|
|
53411
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
53412
|
+
relativeFilePath: fileName,
|
|
53413
|
+
frontmatter: {},
|
|
53414
|
+
body,
|
|
53415
|
+
validate: true,
|
|
53416
|
+
root: true,
|
|
53417
|
+
localRoot
|
|
53418
|
+
});
|
|
53419
|
+
if (isClassOrSubclassOf({
|
|
53420
|
+
candidate: factory.class,
|
|
53421
|
+
base: QwencodeRule
|
|
53422
|
+
})) return new QwencodeRule({
|
|
52534
53423
|
outputRoot: this.outputRoot,
|
|
52535
53424
|
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
52536
53425
|
relativeFilePath: fileName,
|