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
|
@@ -412,6 +412,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
412
412
|
"kiro-cli",
|
|
413
413
|
"kiro-ide",
|
|
414
414
|
"opencode",
|
|
415
|
+
"pi",
|
|
415
416
|
"qwencode",
|
|
416
417
|
"reasonix",
|
|
417
418
|
"rovodev",
|
|
@@ -866,6 +867,7 @@ const ErrorCodes = {
|
|
|
866
867
|
INIT_FAILED: "INIT_FAILED",
|
|
867
868
|
MCP_FAILED: "MCP_FAILED",
|
|
868
869
|
DOCTOR_FAILED: "DOCTOR_FAILED",
|
|
870
|
+
RELEASE_NOTES_FAILED: "RELEASE_NOTES_FAILED",
|
|
869
871
|
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
870
872
|
};
|
|
871
873
|
/**
|
|
@@ -1902,7 +1904,7 @@ function isPlainObject$1(value) {
|
|
|
1902
1904
|
/**
|
|
1903
1905
|
* Type guard to check if a value is an array of strings.
|
|
1904
1906
|
*/
|
|
1905
|
-
function isStringArray$
|
|
1907
|
+
function isStringArray$2(value) {
|
|
1906
1908
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1907
1909
|
}
|
|
1908
1910
|
//#endregion
|
|
@@ -2270,6 +2272,7 @@ const HOOK_EVENTS = [
|
|
|
2270
2272
|
"todoCreated",
|
|
2271
2273
|
"todoCompleted",
|
|
2272
2274
|
"stopFailure",
|
|
2275
|
+
"stopCancelled",
|
|
2273
2276
|
"instructionsLoaded",
|
|
2274
2277
|
"userPromptExpansion",
|
|
2275
2278
|
"postToolBatch",
|
|
@@ -2452,34 +2455,52 @@ const AMP_HOOK_EVENTS = [
|
|
|
2452
2455
|
];
|
|
2453
2456
|
/**
|
|
2454
2457
|
* Hook events supported by Cline's file-based hooks. Cline resolves one
|
|
2455
|
-
* executable per lifecycle event from its hooks directory, and the
|
|
2456
|
-
*
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2459
|
-
* `
|
|
2458
|
+
* executable per lifecycle event from its hooks directory, and the accepted
|
|
2459
|
+
* event names come from two runtimes that read the same directory:
|
|
2460
|
+
*
|
|
2461
|
+
* - The VS Code extension fixes them in `VALID_HOOK_TYPES`
|
|
2462
|
+
* (`apps/vscode/src/core/hooks/utils.ts`): `TaskStart`, `TaskResume`,
|
|
2463
|
+
* `TaskCancel`, `TaskComplete`, `PreToolUse`, `PostToolUse`,
|
|
2464
|
+
* `UserPromptSubmit`, `Notification` and `PreCompact`.
|
|
2465
|
+
* - The SDK/CLI fixes them in `HookConfigFileName`
|
|
2466
|
+
* (`sdk/packages/core/src/hooks/hook-file-config.ts`), which drops
|
|
2467
|
+
* `Notification` but adds `TaskError` (→ `agent_error`) and
|
|
2468
|
+
* `SessionShutdown` (→ `session_shutdown`).
|
|
2469
|
+
*
|
|
2470
|
+
* This set is the union, because `.clinerules/hooks` is in both runtimes'
|
|
2471
|
+
* search paths and a script named for an event the running one does not know
|
|
2472
|
+
* is simply never spawned. That holds for unknown *names* only: for an event a
|
|
2473
|
+
* runtime does know, the SDK/CLI spawns both the extensionless script and its
|
|
2474
|
+
* `.ps1` twin, so each generated script opens with a guard that stands down on
|
|
2475
|
+
* the platform the other one owns — see `generateClineHookScript` and
|
|
2476
|
+
* `generateClineHookPowerShellScript`.
|
|
2460
2477
|
*
|
|
2461
2478
|
* `TaskResume` and `TaskCancel` have no canonical counterpart and stay
|
|
2462
2479
|
* unmapped rather than being approximated by `sessionEnd` / `stop`, whose
|
|
2463
2480
|
* semantics differ.
|
|
2464
2481
|
*
|
|
2465
2482
|
* @see https://github.com/cline/cline/blob/main/apps/vscode/src/core/hooks/utils.ts
|
|
2483
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-config.ts
|
|
2466
2484
|
*/
|
|
2467
2485
|
const CLINE_HOOK_EVENTS = [
|
|
2468
2486
|
"sessionStart",
|
|
2487
|
+
"sessionEnd",
|
|
2469
2488
|
"preToolUse",
|
|
2470
2489
|
"postToolUse",
|
|
2471
2490
|
"beforeSubmitPrompt",
|
|
2472
2491
|
"preCompact",
|
|
2473
2492
|
"notification",
|
|
2474
|
-
"taskCompleted"
|
|
2493
|
+
"taskCompleted",
|
|
2494
|
+
"afterError"
|
|
2475
2495
|
];
|
|
2476
2496
|
/**
|
|
2477
2497
|
* Hook events supported by GitHub Copilot (cloud coding agent).
|
|
2478
2498
|
*
|
|
2479
2499
|
* The events rulesync writes to `.github/hooks/*.json`:
|
|
2480
2500
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted` ← `beforeSubmitPrompt`,
|
|
2481
|
-
* `preToolUse`, `postToolUse`, `agentStop` ← `stop`,
|
|
2482
|
-
* `subagentStop`, `errorOccurred` ← `afterError`,
|
|
2501
|
+
* `preToolUse`, `postToolUse`, `postToolUseFailure`, `agentStop` ← `stop`,
|
|
2502
|
+
* `subagentStart`, `subagentStop`, `errorOccurred` ← `afterError`,
|
|
2503
|
+
* `preCompact`, and `userPromptTransformed` ← `userPromptExpansion`.
|
|
2483
2504
|
*
|
|
2484
2505
|
* `preCompact` and `subagentStart` are authorable because the unified hooks
|
|
2485
2506
|
* reference's per-event "Cloud agent" column says both fire there. That column
|
|
@@ -2488,9 +2509,11 @@ const CLINE_HOOK_EVENTS = [
|
|
|
2488
2509
|
* undo that. `notification` and `permissionRequest` stay out because the same
|
|
2489
2510
|
* column is explicit that they do not fire on the cloud agent.
|
|
2490
2511
|
*
|
|
2491
|
-
*
|
|
2492
|
-
*
|
|
2493
|
-
* agent
|
|
2512
|
+
* `postToolUseFailure` and `userPromptTransformed` are shared with
|
|
2513
|
+
* {@link COPILOTCLI_HOOK_EVENTS}; the same column records both as firing on the
|
|
2514
|
+
* cloud agent, so they are authorable here too. The event surfaces overlap but
|
|
2515
|
+
* the config surfaces do not: `copilot` emits `command` hooks only, while the
|
|
2516
|
+
* CLI adapter also handles `http` and `prompt`.
|
|
2494
2517
|
*
|
|
2495
2518
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
2496
2519
|
*/
|
|
@@ -2500,11 +2523,13 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
2500
2523
|
"beforeSubmitPrompt",
|
|
2501
2524
|
"preToolUse",
|
|
2502
2525
|
"postToolUse",
|
|
2526
|
+
"postToolUseFailure",
|
|
2503
2527
|
"stop",
|
|
2504
2528
|
"subagentStart",
|
|
2505
2529
|
"subagentStop",
|
|
2506
2530
|
"afterError",
|
|
2507
|
-
"preCompact"
|
|
2531
|
+
"preCompact",
|
|
2532
|
+
"userPromptExpansion"
|
|
2508
2533
|
];
|
|
2509
2534
|
/**
|
|
2510
2535
|
* Hook events supported by the GitHub Copilot CLI (`copilotcli-hooks.ts`).
|
|
@@ -2823,6 +2848,7 @@ const GROKCLI_HOOK_EVENTS = [
|
|
|
2823
2848
|
"permissionDenied",
|
|
2824
2849
|
"stop",
|
|
2825
2850
|
"stopFailure",
|
|
2851
|
+
"stopCancelled",
|
|
2826
2852
|
"notification",
|
|
2827
2853
|
"subagentStart",
|
|
2828
2854
|
"subagentStop",
|
|
@@ -2898,16 +2924,10 @@ const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CAN
|
|
|
2898
2924
|
/**
|
|
2899
2925
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
2900
2926
|
*
|
|
2901
|
-
* Hermes validates hook events against a fixed `VALID_HOOKS` set
|
|
2902
|
-
*
|
|
2903
|
-
*
|
|
2904
|
-
*
|
|
2905
|
-
* `on_session_reset`, `subagent_start`, `subagent_stop`, `pre_gateway_dispatch`,
|
|
2906
|
-
* `pre_approval_request`, `post_approval_response`, `transform_tool_result`,
|
|
2907
|
-
* `transform_terminal_output`, `transform_llm_output`, and the three
|
|
2908
|
-
* `kanban_task_*` events. Only the events with a clean 1:1 canonical equivalent
|
|
2909
|
-
* are mapped here. All other native events round-trip through
|
|
2910
|
-
* `hermesagent.hooks`.
|
|
2927
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
2928
|
+
* of v0.20.2 (`v2026.8.16`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
2929
|
+
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
2930
|
+
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
2911
2931
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
2912
2932
|
*/
|
|
2913
2933
|
const HERMESAGENT_HOOK_EVENTS = [
|
|
@@ -2920,6 +2940,21 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
2920
2940
|
"subagentStart",
|
|
2921
2941
|
"subagentStop"
|
|
2922
2942
|
];
|
|
2943
|
+
/**
|
|
2944
|
+
* Hermes Agent's native `VALID_HOOKS` set, in upstream source order.
|
|
2945
|
+
*
|
|
2946
|
+
* The authority is `hermes_cli/plugins.py`'s `VALID_HOOKS`, not the docs: at
|
|
2947
|
+
* v0.20.2 `website/docs/user-guide/features/plugins.md` says "26 lifecycle
|
|
2948
|
+
* events" and then lists 30, omitting the five `on_kanban_*` worker/task/dispatch
|
|
2949
|
+
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
2950
|
+
* Shell hooks are gated on the same set.
|
|
2951
|
+
*
|
|
2952
|
+
* Verified against the tag `v2026.8.16` (v0.20.2): `VALID_HOOKS` holds 37
|
|
2953
|
+
* entries, grown from 23 at `v2026.8.3` (v0.20.0). This list holds 36 of them —
|
|
2954
|
+
* see {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
2955
|
+
*
|
|
2956
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py
|
|
2957
|
+
*/
|
|
2923
2958
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
2924
2959
|
"pre_tool_call",
|
|
2925
2960
|
"post_tool_call",
|
|
@@ -2928,6 +2963,10 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2928
2963
|
"transform_llm_output",
|
|
2929
2964
|
"pre_llm_call",
|
|
2930
2965
|
"post_llm_call",
|
|
2966
|
+
"on_stream_start",
|
|
2967
|
+
"on_stream_delta",
|
|
2968
|
+
"on_stream_end",
|
|
2969
|
+
"on_interim_message",
|
|
2931
2970
|
"pre_verify",
|
|
2932
2971
|
"pre_api_request",
|
|
2933
2972
|
"post_api_request",
|
|
@@ -2936,16 +2975,39 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2936
2975
|
"on_session_end",
|
|
2937
2976
|
"on_session_finalize",
|
|
2938
2977
|
"on_session_reset",
|
|
2978
|
+
"on_skill_lifecycle",
|
|
2939
2979
|
"subagent_start",
|
|
2940
2980
|
"subagent_stop",
|
|
2941
2981
|
"pre_gateway_dispatch",
|
|
2942
2982
|
"pre_approval_request",
|
|
2943
2983
|
"post_approval_response",
|
|
2984
|
+
"pre_transcription",
|
|
2944
2985
|
"kanban_task_claimed",
|
|
2945
2986
|
"kanban_task_completed",
|
|
2946
|
-
"kanban_task_blocked"
|
|
2987
|
+
"kanban_task_blocked",
|
|
2988
|
+
"on_kanban_worker_spawned",
|
|
2989
|
+
"on_kanban_worker_exited",
|
|
2990
|
+
"on_kanban_worker_stale_claim",
|
|
2991
|
+
"on_kanban_task_updated",
|
|
2992
|
+
"on_kanban_dispatch_tick",
|
|
2993
|
+
"gateway_platform_event",
|
|
2994
|
+
"pre_command"
|
|
2947
2995
|
];
|
|
2948
2996
|
/**
|
|
2997
|
+
* `VALID_HOOKS` entries a SHELL hook may not register for. `VALID_HOOKS` doubles
|
|
2998
|
+
* as the shell-hook allow-list, but `SHELL_UNSUPPORTED_HOOKS` is subtracted from
|
|
2999
|
+
* it first: shell hooks cannot return these events' directives, so
|
|
3000
|
+
* `_parse_hooks_block` refuses the registration with a warning rather than
|
|
3001
|
+
* accepting it and dropping the return value.
|
|
3002
|
+
*
|
|
3003
|
+
* rulesync only ever writes shell hooks, so these are excluded from
|
|
3004
|
+
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
3005
|
+
*
|
|
3006
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
3007
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/agent/shell_hooks.py — `_parse_hooks_block`
|
|
3008
|
+
*/
|
|
3009
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
3010
|
+
/**
|
|
2949
3011
|
* Map canonical camelCase event names to Hermes Agent's native `VALID_HOOKS`
|
|
2950
3012
|
* snake_case keys under the `hooks:` block of `~/.hermes/config.yaml`.
|
|
2951
3013
|
*/
|
|
@@ -3220,15 +3282,21 @@ const CANONICAL_TO_AMP_EVENT_NAMES = {
|
|
|
3220
3282
|
beforeSubmitPrompt: "agent.start",
|
|
3221
3283
|
stop: "agent.end"
|
|
3222
3284
|
};
|
|
3223
|
-
/**
|
|
3285
|
+
/**
|
|
3286
|
+
* Map canonical hook events to Cline's hook script file names — the union of
|
|
3287
|
+
* the VS Code extension's `VALID_HOOK_TYPES` and the SDK/CLI's
|
|
3288
|
+
* `HookConfigFileName`, see {@link CLINE_HOOK_EVENTS}.
|
|
3289
|
+
*/
|
|
3224
3290
|
const CANONICAL_TO_CLINE_EVENT_NAMES = {
|
|
3225
3291
|
sessionStart: "TaskStart",
|
|
3292
|
+
sessionEnd: "SessionShutdown",
|
|
3226
3293
|
preToolUse: "PreToolUse",
|
|
3227
3294
|
postToolUse: "PostToolUse",
|
|
3228
3295
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
3229
3296
|
preCompact: "PreCompact",
|
|
3230
3297
|
notification: "Notification",
|
|
3231
|
-
taskCompleted: "TaskComplete"
|
|
3298
|
+
taskCompleted: "TaskComplete",
|
|
3299
|
+
afterError: "TaskError"
|
|
3232
3300
|
};
|
|
3233
3301
|
/**
|
|
3234
3302
|
* Map canonical camelCase event names to Copilot camelCase.
|
|
@@ -3239,11 +3307,13 @@ const CANONICAL_TO_COPILOT_EVENT_NAMES = {
|
|
|
3239
3307
|
beforeSubmitPrompt: "userPromptSubmitted",
|
|
3240
3308
|
preToolUse: "preToolUse",
|
|
3241
3309
|
postToolUse: "postToolUse",
|
|
3310
|
+
postToolUseFailure: "postToolUseFailure",
|
|
3242
3311
|
stop: "agentStop",
|
|
3243
3312
|
subagentStart: "subagentStart",
|
|
3244
3313
|
subagentStop: "subagentStop",
|
|
3245
3314
|
afterError: "errorOccurred",
|
|
3246
|
-
preCompact: "preCompact"
|
|
3315
|
+
preCompact: "preCompact",
|
|
3316
|
+
userPromptExpansion: "userPromptTransformed"
|
|
3247
3317
|
};
|
|
3248
3318
|
/**
|
|
3249
3319
|
* Map Copilot camelCase event names to canonical camelCase.
|
|
@@ -3554,6 +3624,7 @@ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
|
3554
3624
|
permissionDenied: "PermissionDenied",
|
|
3555
3625
|
stop: "Stop",
|
|
3556
3626
|
stopFailure: "StopFailure",
|
|
3627
|
+
stopCancelled: "StopCancelled",
|
|
3557
3628
|
notification: "Notification",
|
|
3558
3629
|
subagentStart: "SubagentStart",
|
|
3559
3630
|
subagentStop: "SubagentStop",
|
|
@@ -3828,6 +3899,25 @@ const McpServerSchema = z.looseObject({
|
|
|
3828
3899
|
kiroAutoApprove: z.optional(z.array(z.string())),
|
|
3829
3900
|
kiroAutoBlock: z.optional(z.array(z.string())),
|
|
3830
3901
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
3902
|
+
/**
|
|
3903
|
+
* The canonical per-server tool allowlist.
|
|
3904
|
+
*
|
|
3905
|
+
* **Collision rule for adapters.** Several tools spell an allowlist natively
|
|
3906
|
+
* under a name that also exists here, so a single server entry can carry both
|
|
3907
|
+
* a canonical field and its native counterpart. What the *native* key means
|
|
3908
|
+
* decides the resolution — not which one was written first:
|
|
3909
|
+
*
|
|
3910
|
+
* - Native key is a redundant spelling of the same concept, and both lists are
|
|
3911
|
+
* additive → **merge** them, so a config that already spells the field
|
|
3912
|
+
* natively keeps working (`kiroAutoApprove`/`kiroAutoBlock` in
|
|
3913
|
+
* `kiro-mcp.ts`).
|
|
3914
|
+
* - Native key is the same concept but rulesync owns the generated value, so
|
|
3915
|
+
* keeping both would be ambiguous → the **canonical value wins** and the
|
|
3916
|
+
* native one is dropped with a warning (`tools` in `copilotcli-mcp.ts`).
|
|
3917
|
+
* - Native key of the same name means something else entirely → **refuse the
|
|
3918
|
+
* canonical value** rather than write a shape the tool misreads (`tools` in
|
|
3919
|
+
* `codexcli-mcp.ts`, where Codex reads it as a per-tool approval table).
|
|
3920
|
+
*/
|
|
3831
3921
|
enabledTools: z.optional(z.array(z.string())),
|
|
3832
3922
|
disabledTools: z.optional(z.array(z.string()))
|
|
3833
3923
|
});
|
|
@@ -4670,8 +4760,65 @@ const AmpPermissionsOverrideSchema = z.looseObject({
|
|
|
4670
4760
|
dangerouslyAllowAll: z.optional(z.boolean())
|
|
4671
4761
|
});
|
|
4672
4762
|
/**
|
|
4763
|
+
* Tool-scoped override block for Pi Coding Agent. Pi has no allow/ask/deny rule
|
|
4764
|
+
* surface: the only repository-syncable tool gate is `defaultTools`, the list of
|
|
4765
|
+
* built-in tools enabled at startup (added in v0.84.2). It is an enable-list
|
|
4766
|
+
* rather than a canonical allow/deny rule set, so it is authored here rather
|
|
4767
|
+
* than mapped onto `permission`, mirroring `KimiCodePermissionsOverrideSchema`'s
|
|
4768
|
+
* `tools.enabled` and `VibePermissionsOverrideSchema`'s `enabled_tools`.
|
|
4769
|
+
*
|
|
4770
|
+
* An empty array is meaningful upstream — it starts Pi with no built-in tools
|
|
4771
|
+
* while keeping extension and SDK custom tools — so it is emitted as written.
|
|
4772
|
+
* CLI flags (`--tools`, `--no-tools`, `--no-builtin-tools`, `--exclude-tools`)
|
|
4773
|
+
* outrank the setting, and a project array **replaces** the global one rather
|
|
4774
|
+
* than merging with it.
|
|
4775
|
+
*
|
|
4776
|
+
* @example
|
|
4777
|
+
* { "defaultTools": ["bash", "edit", "write"] }
|
|
4778
|
+
* @see https://pi.dev/docs/latest/settings
|
|
4779
|
+
*/
|
|
4780
|
+
const PiPermissionsOverrideSchema = z.looseObject({
|
|
4781
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
4782
|
+
defaultTools: z.optional(z.array(z.string()))
|
|
4783
|
+
});
|
|
4784
|
+
/**
|
|
4785
|
+
* The Antigravity CLI's four documented `toolPermission` autonomy presets.
|
|
4786
|
+
* Exported so the import side of `antigravity-cli-permissions.ts` filters
|
|
4787
|
+
* against exactly the values the enum below accepts — an unrecognized value
|
|
4788
|
+
* carried in verbatim would fail validation of the *whole*
|
|
4789
|
+
* `.rulesync/permissions.jsonc`, not just that key.
|
|
4790
|
+
* @see https://antigravity.google/docs/cli/reference
|
|
4791
|
+
*/
|
|
4792
|
+
const ANTIGRAVITY_CLI_TOOL_PERMISSIONS = [
|
|
4793
|
+
"request-review",
|
|
4794
|
+
"proceed-in-sandbox",
|
|
4795
|
+
"always-proceed",
|
|
4796
|
+
"strict"
|
|
4797
|
+
];
|
|
4798
|
+
/**
|
|
4799
|
+
* The Antigravity CLI's three documented `artifactReviewPolicy` values. Shared
|
|
4800
|
+
* with the import filter for the same reason as
|
|
4801
|
+
* {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4802
|
+
* @see https://antigravity.google/docs/cli/settings
|
|
4803
|
+
*/
|
|
4804
|
+
const ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES = [
|
|
4805
|
+
"asks-for-review",
|
|
4806
|
+
"agent-decides",
|
|
4807
|
+
"always-proceed"
|
|
4808
|
+
];
|
|
4809
|
+
/**
|
|
4810
|
+
* The baseline execution modes the CLI persists as `agentMode`. Shared with the
|
|
4811
|
+
* import filter for the same reason as {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4812
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4813
|
+
*/
|
|
4814
|
+
const ANTIGRAVITY_CLI_AGENT_MODES = [
|
|
4815
|
+
"default",
|
|
4816
|
+
"accept-edits",
|
|
4817
|
+
"plan"
|
|
4818
|
+
];
|
|
4819
|
+
/**
|
|
4673
4820
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4674
|
-
* `settings.json` carries
|
|
4821
|
+
* `settings.json` carries five global autonomy/sandbox knobs outside the
|
|
4675
4822
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4676
4823
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4677
4824
|
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
@@ -4679,7 +4826,8 @@ const AmpPermissionsOverrideSchema = z.looseObject({
|
|
|
4679
4826
|
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4680
4827
|
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4681
4828
|
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4682
|
-
* active workspace roots)
|
|
4829
|
+
* active workspace roots), and `agentMode` (the baseline execution mode a session
|
|
4830
|
+
* starts in — `default` / `accept-edits` / `plan`). Antigravity applies the allow/deny
|
|
4683
4831
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4684
4832
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4685
4833
|
* Fields placed here are merged onto the top level of
|
|
@@ -4687,27 +4835,27 @@ const AmpPermissionsOverrideSchema = z.looseObject({
|
|
|
4687
4835
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4688
4836
|
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
4689
4837
|
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4690
|
-
* https://antigravity.google/docs/cli/sandbox
|
|
4691
|
-
* https://antigravity.google/docs/cli/settings
|
|
4838
|
+
* https://antigravity.google/docs/cli/sandbox,
|
|
4839
|
+
* https://antigravity.google/docs/cli/settings and
|
|
4840
|
+
* https://antigravity.google/docs/cli/modes.
|
|
4692
4841
|
*
|
|
4693
4842
|
* @example
|
|
4694
4843
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
4695
4844
|
*/
|
|
4696
4845
|
const AntigravityCliPermissionsOverrideSchema = z.looseObject({
|
|
4697
4846
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
4698
|
-
toolPermission: z.optional(z.enum(
|
|
4699
|
-
"request-review",
|
|
4700
|
-
"proceed-in-sandbox",
|
|
4701
|
-
"always-proceed",
|
|
4702
|
-
"strict"
|
|
4703
|
-
])),
|
|
4847
|
+
toolPermission: z.optional(z.enum(ANTIGRAVITY_CLI_TOOL_PERMISSIONS)),
|
|
4704
4848
|
enableTerminalSandbox: z.optional(z.boolean()),
|
|
4705
|
-
artifactReviewPolicy: z.optional(z.enum(
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4849
|
+
artifactReviewPolicy: z.optional(z.enum(ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES)),
|
|
4850
|
+
allowNonWorkspaceAccess: z.optional(z.boolean()),
|
|
4851
|
+
/**
|
|
4852
|
+
* The baseline execution mode the CLI starts a session in (Agent Mode), the
|
|
4853
|
+
* persisted form of the `/settings` panel's Agent Mode row. Only
|
|
4854
|
+
* `~/.gemini/antigravity-cli/settings.json` is read for it, which is why this
|
|
4855
|
+
* override stays global-only.
|
|
4856
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4857
|
+
*/
|
|
4858
|
+
agentMode: z.optional(z.enum(ANTIGRAVITY_CLI_AGENT_MODES))
|
|
4711
4859
|
});
|
|
4712
4860
|
/**
|
|
4713
4861
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -5017,6 +5165,7 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
5017
5165
|
takt: z.optional(TaktPermissionsOverrideSchema),
|
|
5018
5166
|
amp: z.optional(AmpPermissionsOverrideSchema),
|
|
5019
5167
|
"antigravity-cli": z.optional(AntigravityCliPermissionsOverrideSchema),
|
|
5168
|
+
pi: z.optional(PiPermissionsOverrideSchema),
|
|
5020
5169
|
augmentcode: z.optional(AugmentcodePermissionsOverrideSchema),
|
|
5021
5170
|
kiro: z.optional(KiroPermissionsOverrideSchema),
|
|
5022
5171
|
codexcli: z.optional(CodexcliPermissionsOverrideSchema),
|
|
@@ -5468,6 +5617,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
5468
5617
|
})),
|
|
5469
5618
|
cline: z.optional(z.looseObject({})),
|
|
5470
5619
|
roo: z.optional(z.looseObject({})),
|
|
5620
|
+
amp: z.optional(z.looseObject({})),
|
|
5471
5621
|
devin: z.optional(z.looseObject({
|
|
5472
5622
|
"argument-hint": z.optional(z.string()),
|
|
5473
5623
|
model: z.optional(z.string()),
|
|
@@ -5497,6 +5647,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
5497
5647
|
cursor: z.optional(z.looseObject({
|
|
5498
5648
|
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
5499
5649
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
5650
|
+
"user-invocable": z.optional(z.boolean()),
|
|
5500
5651
|
metadata: z.optional(z.looseObject({}))
|
|
5501
5652
|
})),
|
|
5502
5653
|
factorydroid: z.optional(z.looseObject({
|
|
@@ -5721,6 +5872,11 @@ async function getLocalSkillDirNames(outputRoot) {
|
|
|
5721
5872
|
* default with a per-target value. A defined section value (including `false`)
|
|
5722
5873
|
* always wins over the root default.
|
|
5723
5874
|
*
|
|
5875
|
+
* `devin` also consumes the root-level value — it maps `true` onto a user-only
|
|
5876
|
+
* `triggers` list (see `devin-skill.ts`) — but has no section key of the same
|
|
5877
|
+
* name, so it does not go through this helper. A `devin.triggers` section value
|
|
5878
|
+
* still overrides it.
|
|
5879
|
+
*
|
|
5724
5880
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5725
5881
|
*/
|
|
5726
5882
|
function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
@@ -5731,10 +5887,15 @@ function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
|
5731
5887
|
*
|
|
5732
5888
|
* The rulesync skill frontmatter exposes a root-level `user-invocable` default
|
|
5733
5889
|
* that applies to every tool supporting the flag (claudecode, copilot,
|
|
5734
|
-
* copilotcli, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
|
|
5890
|
+
* copilotcli, cursor, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
|
|
5735
5891
|
* per-target value. A defined section value (including `false`) always wins
|
|
5736
5892
|
* over the root default.
|
|
5737
5893
|
*
|
|
5894
|
+
* `devin` also consumes the root-level value — it maps `false` onto a
|
|
5895
|
+
* model-only `triggers` list (see `devin-skill.ts`) — but has no section key of
|
|
5896
|
+
* the same name, so it does not go through this helper. A `devin.triggers`
|
|
5897
|
+
* section value still overrides it.
|
|
5898
|
+
*
|
|
5738
5899
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5739
5900
|
*/
|
|
5740
5901
|
function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
@@ -7584,6 +7745,22 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7584
7745
|
}
|
|
7585
7746
|
}
|
|
7586
7747
|
},
|
|
7748
|
+
".pi/settings.json": {
|
|
7749
|
+
format: "json",
|
|
7750
|
+
invalidRootPolicy: "error",
|
|
7751
|
+
features: { permissions: {
|
|
7752
|
+
kind: "replace-owned-keys",
|
|
7753
|
+
ownedKeys: ["defaultTools"]
|
|
7754
|
+
} }
|
|
7755
|
+
},
|
|
7756
|
+
".pi/agent/settings.json": {
|
|
7757
|
+
format: "json",
|
|
7758
|
+
invalidRootPolicy: "error",
|
|
7759
|
+
features: { permissions: {
|
|
7760
|
+
kind: "replace-owned-keys",
|
|
7761
|
+
ownedKeys: ["defaultTools"]
|
|
7762
|
+
} }
|
|
7763
|
+
},
|
|
7587
7764
|
".github/copilot/settings.json": {
|
|
7588
7765
|
format: "json",
|
|
7589
7766
|
invalidRootPolicy: "error",
|
|
@@ -11700,7 +11877,8 @@ const KiloCommandFrontmatterSchema = z.looseObject({
|
|
|
11700
11877
|
description: z.optional(z.string()),
|
|
11701
11878
|
agent: z.optional(z.string()),
|
|
11702
11879
|
subtask: z.optional(z.boolean()),
|
|
11703
|
-
model: z.optional(z.string())
|
|
11880
|
+
model: z.optional(z.string()),
|
|
11881
|
+
variant: z.optional(z.string())
|
|
11704
11882
|
});
|
|
11705
11883
|
var KiloCommand = class KiloCommand extends ToolCommand {
|
|
11706
11884
|
frontmatter;
|
|
@@ -12169,6 +12347,8 @@ const PI_RULE_FILE_NAME = "AGENTS.md";
|
|
|
12169
12347
|
const PI_RULE_OVERRIDE_FILE_NAME = "AGENTS.override.md";
|
|
12170
12348
|
const PI_APPEND_SYSTEM_FILE_NAME = "APPEND_SYSTEM.md";
|
|
12171
12349
|
const PI_HOOKS_FILE_NAME = "rulesync-hooks.ts";
|
|
12350
|
+
const PI_SETTINGS_FILE_NAME = "settings.json";
|
|
12351
|
+
const PI_AGENT_DIR_PATH = PI_AGENT_DIR;
|
|
12172
12352
|
//#endregion
|
|
12173
12353
|
//#region src/features/commands/pi-command.ts
|
|
12174
12354
|
/**
|
|
@@ -14093,7 +14273,7 @@ const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
|
14093
14273
|
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
14094
14274
|
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
14095
14275
|
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
14096
|
-
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
14276
|
+
const isEmittableArray = ({ value }) => isStringArray$1(value);
|
|
14097
14277
|
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
14098
14278
|
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
14099
14279
|
/**
|
|
@@ -14402,7 +14582,7 @@ function isStringRecord(value) {
|
|
|
14402
14582
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
14403
14583
|
return Object.values(value).every((v) => typeof v === "string");
|
|
14404
14584
|
}
|
|
14405
|
-
function isStringArray(value) {
|
|
14585
|
+
function isStringArray$1(value) {
|
|
14406
14586
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
14407
14587
|
}
|
|
14408
14588
|
/** Compare object values without letting key order decide the answer. */
|
|
@@ -14434,7 +14614,7 @@ function isSafeStringRecord(value) {
|
|
|
14434
14614
|
* skipped wholesale when that read fails.
|
|
14435
14615
|
*/
|
|
14436
14616
|
function isSafeStringArray(value) {
|
|
14437
|
-
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14617
|
+
return isStringArray$1(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14438
14618
|
}
|
|
14439
14619
|
/**
|
|
14440
14620
|
* A raw string kept only if the canonical field it would land in accepts it,
|
|
@@ -14476,7 +14656,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
14476
14656
|
return {
|
|
14477
14657
|
...url !== void 0 && { url },
|
|
14478
14658
|
...isStringRecord(headers) && { headers },
|
|
14479
|
-
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14659
|
+
...isStringArray$1(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14480
14660
|
};
|
|
14481
14661
|
}
|
|
14482
14662
|
if (hookType === "mcp_tool") {
|
|
@@ -15382,6 +15562,13 @@ function generateClineHookScript({ event, commands }) {
|
|
|
15382
15562
|
`# ${event} hook generated by rulesync — edit .rulesync/hooks.jsonc and regenerate.`,
|
|
15383
15563
|
`# ${CLINE_HOOK_SCRIPT_MARKER}`,
|
|
15384
15564
|
"",
|
|
15565
|
+
"case \"${OSTYPE:-$(uname -s 2>/dev/null || true)}\" in",
|
|
15566
|
+
" msys*|MSYS*|cygwin*|CYGWIN*|MINGW*|mingw*)",
|
|
15567
|
+
` printf '{"cancel": false, "contextModification": "", "errorMessage": ""}\\n'`,
|
|
15568
|
+
" exit 0",
|
|
15569
|
+
" ;;",
|
|
15570
|
+
"esac",
|
|
15571
|
+
"",
|
|
15385
15572
|
"payload=$(cat)",
|
|
15386
15573
|
"cancel=false",
|
|
15387
15574
|
"error_message=''",
|
|
@@ -15395,15 +15582,39 @@ function generateClineHookScript({ event, commands }) {
|
|
|
15395
15582
|
return lines.join("\n");
|
|
15396
15583
|
}
|
|
15397
15584
|
/**
|
|
15398
|
-
* The PowerShell twin of {@link generateClineHookScript}. On Windows
|
|
15399
|
-
* resolves only `<Event>.ps1` and runs it through `powershell -File`,
|
|
15400
|
-
* spellings are written and the platform picks one.
|
|
15585
|
+
* The PowerShell twin of {@link generateClineHookScript}. On Windows the VS Code
|
|
15586
|
+
* extension resolves only `<Event>.ps1` and runs it through `powershell -File`,
|
|
15587
|
+
* so both spellings are written and the platform picks one.
|
|
15588
|
+
*
|
|
15589
|
+
* The SDK/CLI runtime does not pick one. `listHookConfigFiles` dedupes by path,
|
|
15590
|
+
* so `TaskError` and `TaskError.ps1` are two entries naming the same event, and
|
|
15591
|
+
* `createHookCommandMap` appends both to that event's command list without a
|
|
15592
|
+
* per-event dedupe — it then runs every command in the list, spawning a `.ps1`
|
|
15593
|
+
* through `pwsh` on Unix too. That produced noise rather than a second
|
|
15594
|
+
* execution (the body shells out through `cmd /c`, which Unix has no such
|
|
15595
|
+
* thing), but it is still a failure reported on every fire.
|
|
15596
|
+
*
|
|
15597
|
+
* Hence the leading platform guard: off Windows the script answers with the
|
|
15598
|
+
* neutral success payload and exits, leaving the POSIX twin to do the work.
|
|
15599
|
+
* Its counterpart at the top of {@link generateClineHookScript} covers the
|
|
15600
|
+
* quadrant where the duplication is real — Windows with a POSIX shell.
|
|
15601
|
+
* `$IsWindows` only exists in PowerShell 6+, and is `$null` under the Windows
|
|
15602
|
+
* PowerShell 5.1 that `powershell -File` starts — so the guard tests that it is
|
|
15603
|
+
* defined *and* false, rather than `-not $IsWindows`, which would be true on
|
|
15604
|
+
* 5.1 and would no-op the script on the one platform it exists for.
|
|
15605
|
+
*
|
|
15606
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-hooks.ts
|
|
15401
15607
|
*/
|
|
15402
15608
|
function generateClineHookPowerShellScript({ event, commands }) {
|
|
15403
15609
|
const lines = [
|
|
15404
15610
|
`# ${event} hook generated by rulesync — edit .rulesync/hooks.jsonc and regenerate.`,
|
|
15405
15611
|
`# ${CLINE_HOOK_SCRIPT_MARKER}`,
|
|
15406
15612
|
"",
|
|
15613
|
+
"if ($null -ne $IsWindows -and -not $IsWindows) {",
|
|
15614
|
+
` Write-Output '{"cancel": false, "contextModification": "", "errorMessage": ""}'`,
|
|
15615
|
+
" exit 0",
|
|
15616
|
+
"}",
|
|
15617
|
+
"",
|
|
15407
15618
|
"$payload = [Console]::In.ReadToEnd()",
|
|
15408
15619
|
"$cancel = $false",
|
|
15409
15620
|
"$errorMessage = ''",
|
|
@@ -15428,7 +15639,7 @@ const MANAGED_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_CLINE_EVENT_NAMES
|
|
|
15428
15639
|
function parseManifest(fileContent) {
|
|
15429
15640
|
try {
|
|
15430
15641
|
const parsed = JSON.parse(fileContent);
|
|
15431
|
-
if (!isRecord$1(parsed) || !isStringArray$
|
|
15642
|
+
if (!isRecord$1(parsed) || !isStringArray$2(parsed.events)) return null;
|
|
15432
15643
|
return {
|
|
15433
15644
|
generatedBy: "rulesync",
|
|
15434
15645
|
events: parsed.events.filter((event) => MANAGED_EVENT_NAMES.has(event))
|
|
@@ -15465,10 +15676,14 @@ var ClineHookScript = class extends ToolFile {
|
|
|
15465
15676
|
* by the contract, so every generated script carries a marker line and a script
|
|
15466
15677
|
* without it is never overwritten.
|
|
15467
15678
|
*
|
|
15468
|
-
*
|
|
15469
|
-
*
|
|
15679
|
+
* The project hooks directory is in the search paths of both the VS Code
|
|
15680
|
+
* extension and the SDK/CLI, whose accepted event names differ slightly, so the
|
|
15681
|
+
* emitted set is their union ({@link CANONICAL_TO_CLINE_EVENT_NAMES}). Cline's
|
|
15682
|
+
* in-process hook surface (`AgentHooks` from `@cline/core`) is a separate
|
|
15683
|
+
* mechanism this adapter does not target.
|
|
15470
15684
|
*
|
|
15471
15685
|
* @see https://github.com/cline/cline/blob/main/apps/vscode/src/core/hooks/utils.ts
|
|
15686
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-config.ts
|
|
15472
15687
|
*/
|
|
15473
15688
|
var ClineHooks = class ClineHooks extends ToolHooks {
|
|
15474
15689
|
scriptsByEvent;
|
|
@@ -15623,6 +15838,11 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
15623
15838
|
numberPassthroughFields: [{
|
|
15624
15839
|
canonical: "additionalContextLimit",
|
|
15625
15840
|
tool: "additionalContextLimit"
|
|
15841
|
+
}],
|
|
15842
|
+
booleanPassthroughFields: [{
|
|
15843
|
+
canonical: "async",
|
|
15844
|
+
tool: "async",
|
|
15845
|
+
commandOnly: true
|
|
15626
15846
|
}]
|
|
15627
15847
|
};
|
|
15628
15848
|
/**
|
|
@@ -16749,6 +16969,16 @@ const FACTORYDROID_CONVERTER_CONFIG = {
|
|
|
16749
16969
|
subdividesGroup: true
|
|
16750
16970
|
}]
|
|
16751
16971
|
};
|
|
16972
|
+
/** Droid's nine event names, the keys a standalone `hooks.json` is made of. */
|
|
16973
|
+
const FACTORYDROID_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_FACTORYDROID_EVENT_NAMES));
|
|
16974
|
+
/**
|
|
16975
|
+
* Whether a parsed hooks file is the standalone shape — keyed directly by event
|
|
16976
|
+
* name — rather than the `settings.json` shape that wraps the same map in a
|
|
16977
|
+
* `hooks` key.
|
|
16978
|
+
*/
|
|
16979
|
+
function hasFactorydroidEventKey(parsed) {
|
|
16980
|
+
return Object.keys(parsed).some((key) => FACTORYDROID_EVENT_NAMES.has(key));
|
|
16981
|
+
}
|
|
16752
16982
|
var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
16753
16983
|
constructor(params) {
|
|
16754
16984
|
super({
|
|
@@ -16776,14 +17006,6 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
16776
17006
|
}
|
|
16777
17007
|
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
16778
17008
|
const paths = FactorydroidHooks.getSettablePaths({ global });
|
|
16779
|
-
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16780
|
-
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
16781
|
-
let settings;
|
|
16782
|
-
try {
|
|
16783
|
-
settings = JSON.parse(existingContent);
|
|
16784
|
-
} catch (error) {
|
|
16785
|
-
throw new Error(`Failed to parse existing Factory Droid hooks file at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
16786
|
-
}
|
|
16787
17009
|
const config = rulesyncHooks.getJson();
|
|
16788
17010
|
const factorydroidHooks = canonicalToToolHooks({
|
|
16789
17011
|
config,
|
|
@@ -16791,11 +17013,7 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
16791
17013
|
converterConfig: FACTORYDROID_CONVERTER_CONFIG,
|
|
16792
17014
|
logger
|
|
16793
17015
|
});
|
|
16794
|
-
const
|
|
16795
|
-
...settings,
|
|
16796
|
-
hooks: factorydroidHooks
|
|
16797
|
-
};
|
|
16798
|
-
const fileContent = JSON.stringify(merged, null, 2);
|
|
17016
|
+
const fileContent = JSON.stringify(factorydroidHooks, null, 2);
|
|
16799
17017
|
return new FactorydroidHooks({
|
|
16800
17018
|
outputRoot,
|
|
16801
17019
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -16805,14 +17023,14 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
16805
17023
|
});
|
|
16806
17024
|
}
|
|
16807
17025
|
toRulesyncHooks({ logger } = {}) {
|
|
16808
|
-
let
|
|
17026
|
+
let parsed;
|
|
16809
17027
|
try {
|
|
16810
|
-
|
|
17028
|
+
parsed = JSON.parse(this.getFileContent());
|
|
16811
17029
|
} catch (error) {
|
|
16812
17030
|
throw new Error(`Failed to parse Factory Droid hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
16813
17031
|
}
|
|
16814
17032
|
const hooks = toolHooksToCanonical({
|
|
16815
|
-
hooks:
|
|
17033
|
+
hooks: hasFactorydroidEventKey(parsed) ? parsed : parsed.hooks,
|
|
16816
17034
|
converterConfig: FACTORYDROID_CONVERTER_CONFIG,
|
|
16817
17035
|
logger
|
|
16818
17036
|
});
|
|
@@ -16947,18 +17165,7 @@ const GROKCLI_CONVERTER_CONFIG = {
|
|
|
16947
17165
|
tool: "env",
|
|
16948
17166
|
commandOnly: true
|
|
16949
17167
|
}],
|
|
16950
|
-
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
16951
|
-
"sessionStart",
|
|
16952
|
-
"sessionEnd",
|
|
16953
|
-
"beforeSubmitPrompt",
|
|
16954
|
-
"stop",
|
|
16955
|
-
"stopFailure",
|
|
16956
|
-
"notification",
|
|
16957
|
-
"subagentStart",
|
|
16958
|
-
"subagentStop",
|
|
16959
|
-
"preCompact",
|
|
16960
|
-
"postCompact"
|
|
16961
|
-
])
|
|
17168
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["stop", "beforeSubmitPrompt"])
|
|
16962
17169
|
};
|
|
16963
17170
|
/**
|
|
16964
17171
|
* Hooks generator for Grok CLI (xAI Grok Build).
|
|
@@ -17067,6 +17274,7 @@ const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["pre_tool_call", "po
|
|
|
17067
17274
|
const HERMESAGENT_FAIL_CLOSED_EVENT = "pre_tool_call";
|
|
17068
17275
|
const HERMESAGENT_CANONICAL_EVENTS = new Set(HERMESAGENT_HOOK_EVENTS);
|
|
17069
17276
|
const HERMESAGENT_NATIVE_EVENTS = new Set(HERMESAGENT_NATIVE_HOOK_EVENTS);
|
|
17277
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_EVENTS = new Set(HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS);
|
|
17070
17278
|
/**
|
|
17071
17279
|
* Whether an entry of the `hooks:` mapping is a hook-event list rather than one
|
|
17072
17280
|
* of its non-event siblings.
|
|
@@ -17155,7 +17363,8 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
17155
17363
|
}
|
|
17156
17364
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
17157
17365
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
17158
|
-
if (
|
|
17366
|
+
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.`);
|
|
17367
|
+
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.`);
|
|
17159
17368
|
setHermesHookEntries({
|
|
17160
17369
|
result,
|
|
17161
17370
|
event: nativeEvent,
|
|
@@ -22279,7 +22488,7 @@ function isCodexStdioServer(config) {
|
|
|
22279
22488
|
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
22280
22489
|
*/
|
|
22281
22490
|
function isValidRenamedArray(key, value) {
|
|
22282
|
-
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$
|
|
22491
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$2(value);
|
|
22283
22492
|
}
|
|
22284
22493
|
/**
|
|
22285
22494
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
@@ -22799,6 +23008,49 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
22799
23008
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
22800
23009
|
};
|
|
22801
23010
|
/**
|
|
23011
|
+
* Copilot CLI's per-server tool allowlist. `tools` selects which of the
|
|
23012
|
+
* server's tools are exposed — `["*"]` (the default) for all, or a list of
|
|
23013
|
+
* names — so the canonical `enabledTools` is written under that name rather
|
|
23014
|
+
* than passed through as a key the CLI ignores.
|
|
23015
|
+
*
|
|
23016
|
+
* When a server carries both, the canonical `enabledTools` wins and the native
|
|
23017
|
+
* `tools` is dropped with a warning. rulesync owns the generated allowlist, and
|
|
23018
|
+
* the `tools` value most likely present is the upstream default `["*"]` — left
|
|
23019
|
+
* to win, it would silently discard a real allowlist in favour of "expose every
|
|
23020
|
+
* tool", which is the failure this mapping exists to prevent. That is the
|
|
23021
|
+
* "canonical wins" branch of the collision rule documented on `enabledTools` in
|
|
23022
|
+
* `src/types/mcp.ts`; `kiro-mcp.ts` merges instead (its native names are
|
|
23023
|
+
* redundant spellings of additive lists) and `codexcli-mcp.ts` refuses instead
|
|
23024
|
+
* (Codex's same-named key means a different thing).
|
|
23025
|
+
*
|
|
23026
|
+
* `disabledTools` has no counterpart upstream (expressing it would need the
|
|
23027
|
+
* server's full tool list), so the target keeps `supportsDisabledTools: false`
|
|
23028
|
+
* and the processor strips it before it reaches here.
|
|
23029
|
+
*
|
|
23030
|
+
* @see https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
|
|
23031
|
+
*/
|
|
23032
|
+
function renameEnabledToolsToTools({ server, serverName, logger }) {
|
|
23033
|
+
const { enabledTools, ...rest } = server;
|
|
23034
|
+
if (enabledTools === void 0) return rest;
|
|
23035
|
+
if (rest.tools !== void 0) logger?.warn(`[CopilotcliMcp] MCP server "${serverName}" declares both 'tools' and 'enabledTools'; keeping 'enabledTools' and dropping 'tools'.`);
|
|
23036
|
+
return {
|
|
23037
|
+
...rest,
|
|
23038
|
+
tools: enabledTools
|
|
23039
|
+
};
|
|
23040
|
+
}
|
|
23041
|
+
/**
|
|
23042
|
+
* Copilot CLI's `tools` accepts more than the canonical `enabledTools` shape:
|
|
23043
|
+
* `--tools` documents `*`, a comma-separated list, or `""`, so a hand-written or
|
|
23044
|
+
* CLI-written entry can be a bare string. Canonical `enabledTools` is
|
|
23045
|
+
* `string[]`, and `RulesyncMcp` throws on a schema failure — carrying a string
|
|
23046
|
+
* through would reject the WHOLE imported MCP file rather than one key. Anything
|
|
23047
|
+
* that is not a string array is therefore left under `tools` untouched, matching
|
|
23048
|
+
* `codexcli-mcp.ts`'s `isValidRenamedArray` guard on the same class of rename.
|
|
23049
|
+
*/
|
|
23050
|
+
function isStringArray(value) {
|
|
23051
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
23052
|
+
}
|
|
23053
|
+
/**
|
|
22802
23054
|
* Resolves and sets the transport type for each MCP server config.
|
|
22803
23055
|
* GitHub Copilot CLI requires the "type" field for each server. A server it
|
|
22804
23056
|
* cannot express — one that names no transport, or names one it carries no way
|
|
@@ -22842,7 +23094,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22842
23094
|
}
|
|
22843
23095
|
const { httpUrl: _httpUrl, ...rest } = parsed;
|
|
22844
23096
|
result[name] = {
|
|
22845
|
-
...
|
|
23097
|
+
...renameEnabledToolsToTools({
|
|
23098
|
+
server: rest,
|
|
23099
|
+
serverName: name,
|
|
23100
|
+
logger
|
|
23101
|
+
}),
|
|
22846
23102
|
type,
|
|
22847
23103
|
url
|
|
22848
23104
|
};
|
|
@@ -22860,7 +23116,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22860
23116
|
continue;
|
|
22861
23117
|
}
|
|
22862
23118
|
result[name] = {
|
|
22863
|
-
...
|
|
23119
|
+
...renameEnabledToolsToTools({
|
|
23120
|
+
server: local,
|
|
23121
|
+
serverName: name,
|
|
23122
|
+
logger
|
|
23123
|
+
}),
|
|
22864
23124
|
type,
|
|
22865
23125
|
command: head,
|
|
22866
23126
|
...tail.length > 0 && { args: tail }
|
|
@@ -22874,11 +23134,19 @@ function addTypeField(mcpServers, logger) {
|
|
|
22874
23134
|
function removeTypeField(config) {
|
|
22875
23135
|
const result = {};
|
|
22876
23136
|
for (const [name, server] of Object.entries(config.mcpServers ?? {})) {
|
|
22877
|
-
|
|
22878
|
-
|
|
23137
|
+
const { tools, ...withoutTools } = server;
|
|
23138
|
+
const restored = isStringArray(tools) ? {
|
|
23139
|
+
...withoutTools,
|
|
23140
|
+
enabledTools: tools
|
|
23141
|
+
} : {
|
|
23142
|
+
...withoutTools,
|
|
23143
|
+
...tools !== void 0 && { tools }
|
|
23144
|
+
};
|
|
23145
|
+
if (restored.type !== "stdio") {
|
|
23146
|
+
result[name] = restored;
|
|
22879
23147
|
continue;
|
|
22880
23148
|
}
|
|
22881
|
-
const { type: _, ...rest } =
|
|
23149
|
+
const { type: _, ...rest } = restored;
|
|
22882
23150
|
result[name] = rest;
|
|
22883
23151
|
}
|
|
22884
23152
|
return result;
|
|
@@ -23139,6 +23407,78 @@ var CursorMcp = class CursorMcp extends ToolMcp {
|
|
|
23139
23407
|
};
|
|
23140
23408
|
//#endregion
|
|
23141
23409
|
//#region src/features/mcp/deepagents-mcp.ts
|
|
23410
|
+
const TOOL_NAME = "deepagents";
|
|
23411
|
+
/**
|
|
23412
|
+
* Map a canonical transport onto the three dcode accepts.
|
|
23413
|
+
*
|
|
23414
|
+
* `_resolve_server_type` takes `stdio`, `sse` and `http`, plus the aliases
|
|
23415
|
+
* `streamable_http` / `streamable-http` → `http`. Rulesync's canonical
|
|
23416
|
+
* vocabulary is wider: `local` and `ws` are spellings dcode rejects outright,
|
|
23417
|
+
* and a rejected server is dropped at load time with only a log line. `local`
|
|
23418
|
+
* has an exact equivalent so it is translated; `ws` has none and is skipped at
|
|
23419
|
+
* generate time instead, where the warning can still reach the author.
|
|
23420
|
+
*
|
|
23421
|
+
* @see https://docs.langchain.com/oss/deepagents/code/mcp-tools
|
|
23422
|
+
*/
|
|
23423
|
+
function normalizeDeepagentsTransport(transport) {
|
|
23424
|
+
switch (transport) {
|
|
23425
|
+
case "local":
|
|
23426
|
+
case "stdio": return "stdio";
|
|
23427
|
+
case "streamable-http":
|
|
23428
|
+
case "streamable_http":
|
|
23429
|
+
case "http": return "http";
|
|
23430
|
+
case "sse": return "sse";
|
|
23431
|
+
default: return;
|
|
23432
|
+
}
|
|
23433
|
+
}
|
|
23434
|
+
/**
|
|
23435
|
+
* Translate one canonical server into dcode's `.mcp.json` shape, or `null` to
|
|
23436
|
+
* skip it.
|
|
23437
|
+
*
|
|
23438
|
+
* Two upstream constraints from `_validate_tool_filter_fields` are enforced
|
|
23439
|
+
* here, because breaking either one makes dcode drop the whole server: the two
|
|
23440
|
+
* filters are mutually exclusive on a single server, and neither may be an
|
|
23441
|
+
* empty list.
|
|
23442
|
+
*/
|
|
23443
|
+
function toDeepagentsServer({ name, server, logger }) {
|
|
23444
|
+
const rawTransport = server.transport ?? server.type;
|
|
23445
|
+
if (rawTransport === "ws") return warnAndSkipMcpServer({
|
|
23446
|
+
toolName: TOOL_NAME,
|
|
23447
|
+
serverName: name,
|
|
23448
|
+
reason: "the WebSocket transport, which deepagents does not support",
|
|
23449
|
+
logger
|
|
23450
|
+
});
|
|
23451
|
+
const { enabledTools, disabledTools, type: _type, transport, ...rest } = server;
|
|
23452
|
+
const converted = { ...rest };
|
|
23453
|
+
const normalized = normalizeDeepagentsTransport(rawTransport);
|
|
23454
|
+
if (normalized !== void 0) if (transport !== void 0) converted.transport = normalized;
|
|
23455
|
+
else converted.type = normalized;
|
|
23456
|
+
if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
|
|
23457
|
+
toolName: TOOL_NAME,
|
|
23458
|
+
serverName: name,
|
|
23459
|
+
reason: "both enabledTools and disabledTools, which deepagents rejects — pick one",
|
|
23460
|
+
logger
|
|
23461
|
+
});
|
|
23462
|
+
if (enabledTools !== void 0) {
|
|
23463
|
+
if (enabledTools.length === 0) return warnAndSkipMcpServer({
|
|
23464
|
+
toolName: TOOL_NAME,
|
|
23465
|
+
serverName: name,
|
|
23466
|
+
reason: "an empty enabledTools list, which allows no tools at all and which deepagents rejects",
|
|
23467
|
+
logger
|
|
23468
|
+
});
|
|
23469
|
+
converted.allowedTools = enabledTools;
|
|
23470
|
+
} 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.`);
|
|
23471
|
+
else converted.disabledTools = disabledTools;
|
|
23472
|
+
return converted;
|
|
23473
|
+
}
|
|
23474
|
+
/** Lift dcode's spellings back into the canonical model. */
|
|
23475
|
+
function toRulesyncServer(server) {
|
|
23476
|
+
const { allowedTools, ...rest } = server;
|
|
23477
|
+
const converted = { ...rest };
|
|
23478
|
+
for (const key of ["type", "transport"]) if (converted[key] === "streamable_http" || converted[key] === "streamable-http") converted[key] = "http";
|
|
23479
|
+
if (Array.isArray(allowedTools)) converted.enabledTools = allowedTools;
|
|
23480
|
+
return converted;
|
|
23481
|
+
}
|
|
23142
23482
|
var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
23143
23483
|
json;
|
|
23144
23484
|
constructor(params) {
|
|
@@ -23173,12 +23513,22 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
23173
23513
|
validate
|
|
23174
23514
|
});
|
|
23175
23515
|
}
|
|
23176
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
23516
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
23177
23517
|
const paths = this.getSettablePaths({ global });
|
|
23178
23518
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2);
|
|
23519
|
+
const json = JSON.parse(fileContent);
|
|
23520
|
+
const mcpServers = {};
|
|
23521
|
+
for (const [name, server] of Object.entries(rulesyncMcp.getMcpServers())) {
|
|
23522
|
+
const converted = toDeepagentsServer({
|
|
23523
|
+
name,
|
|
23524
|
+
server,
|
|
23525
|
+
logger
|
|
23526
|
+
});
|
|
23527
|
+
if (converted !== null) mcpServers[name] = converted;
|
|
23528
|
+
}
|
|
23179
23529
|
const mcpJson = {
|
|
23180
|
-
...
|
|
23181
|
-
mcpServers
|
|
23530
|
+
...json,
|
|
23531
|
+
mcpServers
|
|
23182
23532
|
};
|
|
23183
23533
|
return new DeepagentsMcp({
|
|
23184
23534
|
outputRoot,
|
|
@@ -23189,7 +23539,9 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
23189
23539
|
});
|
|
23190
23540
|
}
|
|
23191
23541
|
toRulesyncMcp() {
|
|
23192
|
-
|
|
23542
|
+
const servers = isRecord$1(this.json.mcpServers) ? this.json.mcpServers : {};
|
|
23543
|
+
const mcpServers = Object.fromEntries(Object.entries(servers).map(([name, server]) => [name, isRecord$1(server) ? toRulesyncServer(server) : server]));
|
|
23544
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
23193
23545
|
}
|
|
23194
23546
|
validate() {
|
|
23195
23547
|
return {
|
|
@@ -23446,11 +23798,11 @@ function applyGooseStdioFields(ext, config) {
|
|
|
23446
23798
|
if (Array.isArray(command)) {
|
|
23447
23799
|
if (typeof command[0] === "string") ext.cmd = command[0];
|
|
23448
23800
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23449
|
-
const args = isStringArray$
|
|
23801
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23450
23802
|
if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
|
|
23451
23803
|
} else if (typeof command === "string") {
|
|
23452
23804
|
ext.cmd = command;
|
|
23453
|
-
if (isStringArray$
|
|
23805
|
+
if (isStringArray$2(config.args)) ext.args = config.args;
|
|
23454
23806
|
}
|
|
23455
23807
|
if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
|
|
23456
23808
|
}
|
|
@@ -23552,7 +23904,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
23552
23904
|
else if (type === "streamable_http") server.type = "http";
|
|
23553
23905
|
else server.type = "stdio";
|
|
23554
23906
|
if (typeof ext.cmd === "string") server.command = ext.cmd;
|
|
23555
|
-
if (isStringArray$
|
|
23907
|
+
if (isStringArray$2(ext.args)) server.args = ext.args;
|
|
23556
23908
|
if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
|
|
23557
23909
|
if (typeof ext.uri === "string") server.url = ext.uri;
|
|
23558
23910
|
if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
|
|
@@ -23573,11 +23925,11 @@ function buildGoosePluginStdioServer(config) {
|
|
|
23573
23925
|
if (Array.isArray(command)) {
|
|
23574
23926
|
if (typeof command[0] === "string") server.command = command[0];
|
|
23575
23927
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23576
|
-
const args = isStringArray$
|
|
23928
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23577
23929
|
if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
|
|
23578
23930
|
} else if (typeof command === "string") {
|
|
23579
23931
|
server.command = command;
|
|
23580
|
-
if (isStringArray$
|
|
23932
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
23581
23933
|
}
|
|
23582
23934
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
23583
23935
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -23909,7 +24261,7 @@ function copyHermesOauth(source) {
|
|
|
23909
24261
|
"client_secret"
|
|
23910
24262
|
]) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
23911
24263
|
if (typeof source.redirect_port === "number") oauth.redirect_port = source.redirect_port;
|
|
23912
|
-
if (isStringArray$
|
|
24264
|
+
if (isStringArray$2(source.scopes)) oauth.scopes = source.scopes;
|
|
23913
24265
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
23914
24266
|
}
|
|
23915
24267
|
function copyHermesAdvancedFields(source, target) {
|
|
@@ -23918,7 +24270,7 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
23918
24270
|
target.auth = source.auth;
|
|
23919
24271
|
copied = true;
|
|
23920
24272
|
}
|
|
23921
|
-
if (typeof source.client_cert === "string" || isStringArray$
|
|
24273
|
+
if (typeof source.client_cert === "string" || isStringArray$2(source.client_cert)) {
|
|
23922
24274
|
target.client_cert = source.client_cert;
|
|
23923
24275
|
copied = true;
|
|
23924
24276
|
}
|
|
@@ -23987,8 +24339,8 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
23987
24339
|
*/
|
|
23988
24340
|
function buildHermesToolsBlock(config) {
|
|
23989
24341
|
const tools = {};
|
|
23990
|
-
if (isStringArray$
|
|
23991
|
-
if (isStringArray$
|
|
24342
|
+
if (isStringArray$2(config.enabledTools)) tools.include = config.enabledTools;
|
|
24343
|
+
if (isStringArray$2(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
23992
24344
|
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
23993
24345
|
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
23994
24346
|
return tools;
|
|
@@ -24000,8 +24352,8 @@ function buildHermesToolsBlock(config) {
|
|
|
24000
24352
|
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
24001
24353
|
*/
|
|
24002
24354
|
function applyHermesToolsBlock(hermesTools, server) {
|
|
24003
|
-
if (isStringArray$
|
|
24004
|
-
if (isStringArray$
|
|
24355
|
+
if (isStringArray$2(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
24356
|
+
if (isStringArray$2(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
24005
24357
|
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
24006
24358
|
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
24007
24359
|
}
|
|
@@ -24027,11 +24379,11 @@ function convertServerToHermes(config) {
|
|
|
24027
24379
|
if (Array.isArray(command)) {
|
|
24028
24380
|
if (typeof command[0] === "string") out.command = command[0];
|
|
24029
24381
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
24030
|
-
const args = isStringArray$
|
|
24382
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
24031
24383
|
if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
|
|
24032
24384
|
} else if (typeof command === "string") {
|
|
24033
24385
|
out.command = command;
|
|
24034
|
-
if (isStringArray$
|
|
24386
|
+
if (isStringArray$2(config.args)) out.args = config.args;
|
|
24035
24387
|
}
|
|
24036
24388
|
if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
|
|
24037
24389
|
} else if (url !== void 0) {
|
|
@@ -24081,7 +24433,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
24081
24433
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
24082
24434
|
const server = {};
|
|
24083
24435
|
if (typeof config.command === "string") server.command = config.command;
|
|
24084
|
-
if (isStringArray$
|
|
24436
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
24085
24437
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
24086
24438
|
if (typeof config.url === "string") server.url = config.url;
|
|
24087
24439
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
@@ -25039,13 +25391,17 @@ function mergeToolLists(...lists) {
|
|
|
25039
25391
|
*
|
|
25040
25392
|
* Both native names are documented per-server fields, so a config that already
|
|
25041
25393
|
* spells them natively keeps working: the two lists are merged rather than
|
|
25042
|
-
* one overwriting the other.
|
|
25394
|
+
* one overwriting the other. That is the "merge" branch of the collision rule
|
|
25395
|
+
* documented on `enabledTools` in `src/types/mcp.ts` — it applies here because
|
|
25396
|
+
* the native names are redundant spellings of the same additive lists;
|
|
25397
|
+
* `copilotcli-mcp.ts` lets the canonical value win instead, and
|
|
25398
|
+
* `codexcli-mcp.ts` refuses the canonical value instead.
|
|
25043
25399
|
* @see https://kiro.dev/docs/mcp/configuration/
|
|
25044
25400
|
*/
|
|
25045
25401
|
function toKiroMcpServers(servers) {
|
|
25046
25402
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25047
25403
|
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
25048
|
-
const autoApprove = mergeToolLists(isStringArray$
|
|
25404
|
+
const autoApprove = mergeToolLists(isStringArray$2(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
25049
25405
|
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
25050
25406
|
return [name, {
|
|
25051
25407
|
...rest,
|
|
@@ -25069,7 +25425,7 @@ function fromKiroMcpServers(servers) {
|
|
|
25069
25425
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25070
25426
|
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
25071
25427
|
const { autoApprove, ...rest } = server;
|
|
25072
|
-
if (!isStringArray$
|
|
25428
|
+
if (!isStringArray$2(autoApprove)) return [name, server];
|
|
25073
25429
|
return [name, {
|
|
25074
25430
|
...rest,
|
|
25075
25431
|
kiroAutoApprove: autoApprove
|
|
@@ -26211,7 +26567,7 @@ async function readRovodevConfigYaml({ outputRoot }) {
|
|
|
26211
26567
|
}
|
|
26212
26568
|
function disabledNamesOf(config) {
|
|
26213
26569
|
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
26214
|
-
return isStringArray$
|
|
26570
|
+
return isStringArray$2(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
26215
26571
|
}
|
|
26216
26572
|
/**
|
|
26217
26573
|
* Auxiliary writer for the `mcp:` block of `.rovodev/config.yml` (project) /
|
|
@@ -26344,7 +26700,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
26344
26700
|
return [];
|
|
26345
26701
|
}
|
|
26346
26702
|
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
26347
|
-
const existingDisabled = isStringArray$
|
|
26703
|
+
const existingDisabled = isStringArray$2(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
26348
26704
|
const managedNameSet = new Set(managedNames);
|
|
26349
26705
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
26350
26706
|
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.`);
|
|
@@ -27186,7 +27542,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27186
27542
|
meta: {
|
|
27187
27543
|
supportsProject: true,
|
|
27188
27544
|
supportsGlobal: true,
|
|
27189
|
-
supportsEnabledTools:
|
|
27545
|
+
supportsEnabledTools: true,
|
|
27190
27546
|
supportsDisabledTools: false
|
|
27191
27547
|
}
|
|
27192
27548
|
}],
|
|
@@ -27204,8 +27560,8 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27204
27560
|
meta: {
|
|
27205
27561
|
supportsProject: true,
|
|
27206
27562
|
supportsGlobal: true,
|
|
27207
|
-
supportsEnabledTools:
|
|
27208
|
-
supportsDisabledTools:
|
|
27563
|
+
supportsEnabledTools: true,
|
|
27564
|
+
supportsDisabledTools: true
|
|
27209
27565
|
}
|
|
27210
27566
|
}],
|
|
27211
27567
|
["factorydroid", {
|
|
@@ -27214,7 +27570,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27214
27570
|
supportsProject: true,
|
|
27215
27571
|
supportsGlobal: true,
|
|
27216
27572
|
supportsEnabledTools: false,
|
|
27217
|
-
supportsDisabledTools:
|
|
27573
|
+
supportsDisabledTools: true
|
|
27218
27574
|
}
|
|
27219
27575
|
}],
|
|
27220
27576
|
["goose", {
|
|
@@ -27340,7 +27696,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27340
27696
|
supportsProject: true,
|
|
27341
27697
|
supportsGlobal: false,
|
|
27342
27698
|
supportsEnabledTools: false,
|
|
27343
|
-
supportsDisabledTools:
|
|
27699
|
+
supportsDisabledTools: true
|
|
27344
27700
|
}
|
|
27345
27701
|
}],
|
|
27346
27702
|
["zoocode", {
|
|
@@ -27349,7 +27705,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27349
27705
|
supportsProject: true,
|
|
27350
27706
|
supportsGlobal: false,
|
|
27351
27707
|
supportsEnabledTools: false,
|
|
27352
|
-
supportsDisabledTools:
|
|
27708
|
+
supportsDisabledTools: true
|
|
27353
27709
|
}
|
|
27354
27710
|
}],
|
|
27355
27711
|
["rovodev", {
|
|
@@ -28010,6 +28366,34 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
28010
28366
|
//#endregion
|
|
28011
28367
|
//#region src/features/permissions/antigravity-cli-permissions.ts
|
|
28012
28368
|
/**
|
|
28369
|
+
* Top-level `~/.gemini/antigravity-cli/settings.json` keys the `antigravity-cli`
|
|
28370
|
+
* permissions override may author. They are siblings of `permissions`, passed
|
|
28371
|
+
* through verbatim in both directions.
|
|
28372
|
+
*/
|
|
28373
|
+
const ANTIGRAVITY_CLI_OVERRIDE_KEYS = [
|
|
28374
|
+
"toolPermission",
|
|
28375
|
+
"enableTerminalSandbox",
|
|
28376
|
+
"artifactReviewPolicy",
|
|
28377
|
+
"allowNonWorkspaceAccess",
|
|
28378
|
+
"agentMode"
|
|
28379
|
+
];
|
|
28380
|
+
const moduleLogger$2 = fallbackLogger;
|
|
28381
|
+
/**
|
|
28382
|
+
* Narrow an imported `settings.json` value to one of the documented values of
|
|
28383
|
+
* an enum-typed override key. The override schema declares `toolPermission`,
|
|
28384
|
+
* `artifactReviewPolicy` and `agentMode` as `z.enum`, so carrying an
|
|
28385
|
+
* unrecognized value through — a hand-written typo, or a preset a newer
|
|
28386
|
+
* Antigravity release added — would fail validation of the whole imported
|
|
28387
|
+
* `.rulesync/permissions.jsonc` instead of dropping just that key. A dropped
|
|
28388
|
+
* value is warned about rather than discarded silently, so a preset rulesync
|
|
28389
|
+
* does not know about yet is visible instead of quietly missing.
|
|
28390
|
+
*/
|
|
28391
|
+
function pickDocumentedValue({ key, value, documented }) {
|
|
28392
|
+
if (typeof value !== "string") return void 0;
|
|
28393
|
+
if (documented.includes(value)) return value;
|
|
28394
|
+
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.`);
|
|
28395
|
+
}
|
|
28396
|
+
/**
|
|
28013
28397
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
28014
28398
|
* Antigravity CLI Fine-Grained Permissions Engine action vocabulary
|
|
28015
28399
|
* (`read_file`, `write_file`, `read_url`, `command`, `mcp`, plus `execute_url` /
|
|
@@ -28084,9 +28468,10 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
28084
28468
|
* file (global scope only). The file holds other CLI settings besides
|
|
28085
28469
|
* permissions, so it is never deleted.
|
|
28086
28470
|
*
|
|
28087
|
-
*
|
|
28471
|
+
* Five CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
28088
28472
|
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
28089
|
-
* `artifactReviewPolicy` and `
|
|
28473
|
+
* `artifactReviewPolicy`, `allowNonWorkspaceAccess` and `agentMode` (the
|
|
28474
|
+
* baseline execution mode) — are authored and
|
|
28090
28475
|
* round-tripped through the `antigravity-cli` permissions override (see
|
|
28091
28476
|
* `AntigravityCliPermissionsOverrideSchema`).
|
|
28092
28477
|
*/
|
|
@@ -28150,10 +28535,10 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28150
28535
|
permissions: mergedPermissions
|
|
28151
28536
|
};
|
|
28152
28537
|
const override = config["antigravity-cli"];
|
|
28153
|
-
|
|
28154
|
-
|
|
28155
|
-
|
|
28156
|
-
|
|
28538
|
+
for (const key of ANTIGRAVITY_CLI_OVERRIDE_KEYS) {
|
|
28539
|
+
const value = override?.[key];
|
|
28540
|
+
if (value !== void 0) merged[key] = value;
|
|
28541
|
+
}
|
|
28157
28542
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
28158
28543
|
return new AntigravityCliPermissions({
|
|
28159
28544
|
outputRoot,
|
|
@@ -28178,10 +28563,26 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28178
28563
|
deny: permissions.deny ?? []
|
|
28179
28564
|
});
|
|
28180
28565
|
const override = {};
|
|
28181
|
-
|
|
28566
|
+
const toolPermission = pickDocumentedValue({
|
|
28567
|
+
key: "toolPermission",
|
|
28568
|
+
value: settings.toolPermission,
|
|
28569
|
+
documented: ANTIGRAVITY_CLI_TOOL_PERMISSIONS
|
|
28570
|
+
});
|
|
28571
|
+
if (toolPermission !== void 0) override.toolPermission = toolPermission;
|
|
28182
28572
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
28183
|
-
|
|
28573
|
+
const artifactReviewPolicy = pickDocumentedValue({
|
|
28574
|
+
key: "artifactReviewPolicy",
|
|
28575
|
+
value: settings.artifactReviewPolicy,
|
|
28576
|
+
documented: ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES
|
|
28577
|
+
});
|
|
28578
|
+
if (artifactReviewPolicy !== void 0) override.artifactReviewPolicy = artifactReviewPolicy;
|
|
28184
28579
|
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
28580
|
+
const agentMode = pickDocumentedValue({
|
|
28581
|
+
key: "agentMode",
|
|
28582
|
+
value: settings.agentMode,
|
|
28583
|
+
documented: ANTIGRAVITY_CLI_AGENT_MODES
|
|
28584
|
+
});
|
|
28585
|
+
if (agentMode !== void 0) override.agentMode = agentMode;
|
|
28185
28586
|
const result = { ...config };
|
|
28186
28587
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
28187
28588
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -29007,6 +29408,119 @@ function deepMergeRecords(base, patch) {
|
|
|
29007
29408
|
}
|
|
29008
29409
|
return merged;
|
|
29009
29410
|
}
|
|
29411
|
+
/**
|
|
29412
|
+
* `sandbox.*` paths Claude Code honors only from user settings, managed settings
|
|
29413
|
+
* and the `--settings` CLI flag. Written into a project `.claude/settings.json`
|
|
29414
|
+
* they are silently ignored, so a committed file would read as though it
|
|
29415
|
+
* enforced a sandbox policy while doing nothing — security-relevant for
|
|
29416
|
+
* `network.strictAllowlist` and the credential-masking keys in particular.
|
|
29417
|
+
* Generation therefore drops them at project scope with a per-key warning and
|
|
29418
|
+
* emits them only under `--global`.
|
|
29419
|
+
*
|
|
29420
|
+
* Deliberately NOT listed:
|
|
29421
|
+
* - `bwrapPath` / `socatPath`: v2.1.232 added them to the managed-settings
|
|
29422
|
+
* approval dialog, which is a consent prompt, not a project-scope rejection.
|
|
29423
|
+
* - `credentials.envVars` / `credentials.files`: the ignored-at-project-scope
|
|
29424
|
+
* unit is the individual entry's mode, not the settings key, and the same
|
|
29425
|
+
* lists carry `deny` entries that project settings *do* honor — dropping a
|
|
29426
|
+
* whole list would remove real restrictions. `stripProjectIgnoredMaskEntries`
|
|
29427
|
+
* filters those lists per entry instead.
|
|
29428
|
+
*
|
|
29429
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
29430
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md — v2.1.232 scoped `sandbox.ripgrep`
|
|
29431
|
+
*/
|
|
29432
|
+
const CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS = [
|
|
29433
|
+
["filesystem", "disabled"],
|
|
29434
|
+
["network", "strictAllowlist"],
|
|
29435
|
+
["network", "tlsTerminate"],
|
|
29436
|
+
["credentials", "allowPlaintextInject"],
|
|
29437
|
+
["credentials", "awsPairs"],
|
|
29438
|
+
["credentials", "sigv4"],
|
|
29439
|
+
["allowAppleEvents"],
|
|
29440
|
+
["ripgrep"]
|
|
29441
|
+
];
|
|
29442
|
+
/**
|
|
29443
|
+
* Copy of the authored `sandbox` override with the user/managed-only paths
|
|
29444
|
+
* removed, warning once per dropped path. Only the override copy is filtered —
|
|
29445
|
+
* a value already hand-written in the target file is left untouched, matching
|
|
29446
|
+
* the `qwencode` `security.allowPrivateNetworkHooks` precedent.
|
|
29447
|
+
*/
|
|
29448
|
+
function stripGlobalOnlySandboxPaths({ sandbox, relativeFilePath, logger }) {
|
|
29449
|
+
const filtered = structuredClone(sandbox);
|
|
29450
|
+
for (const path of CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS) {
|
|
29451
|
+
const leaf = path.at(-1);
|
|
29452
|
+
if (leaf === void 0) continue;
|
|
29453
|
+
const parentPath = path.slice(0, -1);
|
|
29454
|
+
let parent = filtered;
|
|
29455
|
+
for (const segment of parentPath) {
|
|
29456
|
+
const next = parent[segment];
|
|
29457
|
+
if (!isPlainRecord(next)) {
|
|
29458
|
+
parent = {};
|
|
29459
|
+
break;
|
|
29460
|
+
}
|
|
29461
|
+
parent = next;
|
|
29462
|
+
}
|
|
29463
|
+
if (parent[leaf] === void 0) continue;
|
|
29464
|
+
delete parent[leaf];
|
|
29465
|
+
const [container] = parentPath;
|
|
29466
|
+
if (container !== void 0 && isPlainRecord(filtered[container])) {
|
|
29467
|
+
if (Object.keys(filtered[container]).length === 0) delete filtered[container];
|
|
29468
|
+
}
|
|
29469
|
+
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.`);
|
|
29470
|
+
}
|
|
29471
|
+
return filtered;
|
|
29472
|
+
}
|
|
29473
|
+
/** The `sandbox.credentials` lists whose entries accept `"mode": "mask"`. */
|
|
29474
|
+
const CLAUDECODE_MASKABLE_CREDENTIAL_LISTS = ["envVars", "files"];
|
|
29475
|
+
/**
|
|
29476
|
+
* Copy of the authored `sandbox` override with the `credentials.envVars` /
|
|
29477
|
+
* `credentials.files` entries whose `mode` is `"mask"` removed, warning once per
|
|
29478
|
+
* list.
|
|
29479
|
+
*
|
|
29480
|
+
* Masking authorizes the sandbox proxy to send the real credential to the listed
|
|
29481
|
+
* hosts, so Claude Code honors it only from user settings, managed settings and
|
|
29482
|
+
* the `--settings` CLI flag; a `mask` entry in a repository's
|
|
29483
|
+
* `.claude/settings.json` is ignored outright. Keeping it would read as though
|
|
29484
|
+
* the credential were masked while nothing protects it — the one project-scope
|
|
29485
|
+
* gap whose consequence is a live credential leaving the sandbox unmasked.
|
|
29486
|
+
*
|
|
29487
|
+
* Filtered per entry rather than per key, because the same lists carry `deny`
|
|
29488
|
+
* entries that project settings *do* honor. `mode` is matched exactly: an entry
|
|
29489
|
+
* Claude Code cannot read as `mask` is not treated as one either (it degrades
|
|
29490
|
+
* such entries to `deny`), so the "reads as masked but isn't" state this guards
|
|
29491
|
+
* against cannot slip through a differently-spelled value.
|
|
29492
|
+
*
|
|
29493
|
+
* Like `stripGlobalOnlySandboxPaths`, only the override copy is filtered — a
|
|
29494
|
+
* value already in the target file is left untouched, which is why the warning
|
|
29495
|
+
* points at it.
|
|
29496
|
+
*
|
|
29497
|
+
* @see https://code.claude.com/docs/en/sandboxing — "`mask` entries … are all
|
|
29498
|
+
* ignored in a repository's `.claude/settings.json` or
|
|
29499
|
+
* `.claude/settings.local.json`"; the file-entry section states the same
|
|
29500
|
+
* settings-source restriction applies there.
|
|
29501
|
+
*/
|
|
29502
|
+
function stripProjectIgnoredMaskEntries({ sandbox, relativeFilePath, logger }) {
|
|
29503
|
+
const credentials = sandbox.credentials;
|
|
29504
|
+
if (!isPlainRecord(credentials)) return sandbox;
|
|
29505
|
+
const filteredCredentials = { ...credentials };
|
|
29506
|
+
let changed = false;
|
|
29507
|
+
for (const listKey of CLAUDECODE_MASKABLE_CREDENTIAL_LISTS) {
|
|
29508
|
+
const list = filteredCredentials[listKey];
|
|
29509
|
+
if (!Array.isArray(list)) continue;
|
|
29510
|
+
const kept = list.filter((entry) => !(isPlainRecord(entry) && entry.mode === "mask"));
|
|
29511
|
+
if (kept.length === list.length) continue;
|
|
29512
|
+
changed = true;
|
|
29513
|
+
const dropped = list.length - kept.length;
|
|
29514
|
+
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.`);
|
|
29515
|
+
if (kept.length === 0) delete filteredCredentials[listKey];
|
|
29516
|
+
else filteredCredentials[listKey] = kept;
|
|
29517
|
+
}
|
|
29518
|
+
if (!changed) return sandbox;
|
|
29519
|
+
const filtered = { ...sandbox };
|
|
29520
|
+
if (Object.keys(filteredCredentials).length === 0) delete filtered.credentials;
|
|
29521
|
+
else filtered.credentials = filteredCredentials;
|
|
29522
|
+
return filtered;
|
|
29523
|
+
}
|
|
29010
29524
|
const CLAUDE_PATH_RULE_ALIASES = {
|
|
29011
29525
|
Write: "Edit",
|
|
29012
29526
|
NotebookEdit: "Edit",
|
|
@@ -29060,7 +29574,7 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29060
29574
|
validate
|
|
29061
29575
|
});
|
|
29062
29576
|
}
|
|
29063
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
|
|
29577
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
29064
29578
|
const paths = ClaudecodePermissions.getSettablePaths();
|
|
29065
29579
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
29066
29580
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -29084,7 +29598,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29084
29598
|
};
|
|
29085
29599
|
}
|
|
29086
29600
|
const overrideSandbox = config.claudecode?.sandbox;
|
|
29087
|
-
if (isPlainRecord(overrideSandbox))
|
|
29601
|
+
if (isPlainRecord(overrideSandbox)) {
|
|
29602
|
+
const scopedSandbox = global ? overrideSandbox : stripProjectIgnoredMaskEntries({
|
|
29603
|
+
sandbox: stripGlobalOnlySandboxPaths({
|
|
29604
|
+
sandbox: overrideSandbox,
|
|
29605
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29606
|
+
logger
|
|
29607
|
+
}),
|
|
29608
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29609
|
+
logger
|
|
29610
|
+
});
|
|
29611
|
+
if (Object.keys(scopedSandbox).length > 0) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, scopedSandbox);
|
|
29612
|
+
}
|
|
29088
29613
|
const managedToolNames = managedClaudeToolNames(config);
|
|
29089
29614
|
const merged = applyPermissions({
|
|
29090
29615
|
settings,
|
|
@@ -31261,7 +31786,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
31261
31786
|
function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
31262
31787
|
const permission = {};
|
|
31263
31788
|
for (const key of GOOSE_PERMISSION_LIST_KEYS) {
|
|
31264
|
-
const toolNames = isStringArray$
|
|
31789
|
+
const toolNames = isStringArray$2(userPermission[key]) ? userPermission[key] : [];
|
|
31265
31790
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
31266
31791
|
for (const toolName of toolNames) {
|
|
31267
31792
|
const category = GOOSE_TO_RULESYNC_TOOL_NAME[toolName] ?? toolName;
|
|
@@ -31276,6 +31801,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
31276
31801
|
const GROKCLI_UI_KEY = "ui";
|
|
31277
31802
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
31278
31803
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
31804
|
+
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
31279
31805
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
31280
31806
|
const MCP_CANONICAL_PREFIX$1 = "mcp__";
|
|
31281
31807
|
const CATEGORY_TO_GROK_TOOL = {
|
|
@@ -31489,8 +32015,9 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
31489
32015
|
deny: buckets.deny,
|
|
31490
32016
|
ask: buckets.ask
|
|
31491
32017
|
};
|
|
31492
|
-
const
|
|
31493
|
-
|
|
32018
|
+
const existingUi = isRecord$1(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {};
|
|
32019
|
+
const uiPatch = global && existingUi[GROKCLI_PERMISSION_MODE_KEY] !== GROKCLI_AUTO_PERMISSION_MODE ? { [GROKCLI_UI_KEY]: {
|
|
32020
|
+
...existingUi,
|
|
31494
32021
|
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
31495
32022
|
} } : {};
|
|
31496
32023
|
return new GrokcliPermissions({
|
|
@@ -31552,7 +32079,7 @@ const ACTION_RANK = {
|
|
|
31552
32079
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
31553
32080
|
*/
|
|
31554
32081
|
function unmanagedEntries(existingPermission, key) {
|
|
31555
|
-
return (isStringArray$
|
|
32082
|
+
return (isStringArray$2(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
31556
32083
|
}
|
|
31557
32084
|
/**
|
|
31558
32085
|
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
@@ -31595,9 +32122,9 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
31595
32122
|
* described more than once resolves to the strictest action.
|
|
31596
32123
|
*/
|
|
31597
32124
|
function parseGrokPermissionArrays(permission) {
|
|
31598
|
-
const allow = isStringArray$
|
|
31599
|
-
const deny = isStringArray$
|
|
31600
|
-
const ask = isStringArray$
|
|
32125
|
+
const allow = isStringArray$2(permission.allow) ? permission.allow : void 0;
|
|
32126
|
+
const deny = isStringArray$2(permission.deny) ? permission.deny : void 0;
|
|
32127
|
+
const ask = isStringArray$2(permission.ask) ? permission.ask : void 0;
|
|
31601
32128
|
const rules = Array.isArray(permission[GROKCLI_PERMISSION_RULES_KEY]) ? permission[GROKCLI_PERMISSION_RULES_KEY] : void 0;
|
|
31602
32129
|
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
31603
32130
|
const result = {};
|
|
@@ -31779,20 +32306,20 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
31779
32306
|
const permission = clonePermissionBlock(provenance.permission);
|
|
31780
32307
|
reconcileCommandAllowlist({
|
|
31781
32308
|
permission,
|
|
31782
|
-
commandAllowlist: isStringArray$
|
|
32309
|
+
commandAllowlist: isStringArray$2(config.command_allowlist) ? config.command_allowlist : []
|
|
31783
32310
|
});
|
|
31784
32311
|
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
31785
32312
|
reconcileNativeDenies({
|
|
31786
32313
|
permission,
|
|
31787
32314
|
category: "bash",
|
|
31788
|
-
patterns: isStringArray$
|
|
32315
|
+
patterns: isStringArray$2(approvals.deny) ? approvals.deny : []
|
|
31789
32316
|
});
|
|
31790
32317
|
const security = isRecord$1(config.security) ? config.security : {};
|
|
31791
32318
|
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
31792
32319
|
reconcileNativeDenies({
|
|
31793
32320
|
permission,
|
|
31794
32321
|
category: "webfetch",
|
|
31795
|
-
patterns: websiteBlocklist.enabled === true && isStringArray$
|
|
32322
|
+
patterns: websiteBlocklist.enabled === true && isStringArray$2(websiteBlocklist.domains) ? websiteBlocklist.domains : []
|
|
31796
32323
|
});
|
|
31797
32324
|
removeEmptyCategories(permission);
|
|
31798
32325
|
const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
|
|
@@ -33220,6 +33747,115 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
33220
33747
|
}
|
|
33221
33748
|
};
|
|
33222
33749
|
//#endregion
|
|
33750
|
+
//#region src/features/permissions/pi-permissions.ts
|
|
33751
|
+
/** The one `settings.json` key this feature owns. */
|
|
33752
|
+
const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
33753
|
+
/**
|
|
33754
|
+
* Permissions generator for Pi Coding Agent.
|
|
33755
|
+
*
|
|
33756
|
+
* Pi exposes no allow/ask/deny rule surface, so no canonical permission
|
|
33757
|
+
* category maps onto it. Its one repository-syncable tool gate is `defaultTools` —
|
|
33758
|
+
* the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
|
|
33759
|
+
* permissions override.
|
|
33760
|
+
*
|
|
33761
|
+
* Scope semantics are the **opposite** of the union-merge most targets use:
|
|
33762
|
+
* a project `defaultTools` array *replaces* the global array rather than adding
|
|
33763
|
+
* to it, so the two scopes are written independently and never combined.
|
|
33764
|
+
* Project scope writes `.pi/settings.json`; global scope writes
|
|
33765
|
+
* `~/.pi/agent/settings.json`.
|
|
33766
|
+
*
|
|
33767
|
+
* The file is hand-edited and holds many unrelated keys (`theme`,
|
|
33768
|
+
* `defaultModel`, `packages`, `sessionDir`, ...), so writes go through the
|
|
33769
|
+
* shared-config gateway with `defaultTools` as the only owned key, and the file
|
|
33770
|
+
* is never deleted.
|
|
33771
|
+
*
|
|
33772
|
+
* @see https://pi.dev/docs/latest/settings
|
|
33773
|
+
*/
|
|
33774
|
+
var PiPermissions = class PiPermissions extends ToolPermissions {
|
|
33775
|
+
constructor(params) {
|
|
33776
|
+
super({
|
|
33777
|
+
...params,
|
|
33778
|
+
fileContent: params.fileContent ?? "{}"
|
|
33779
|
+
});
|
|
33780
|
+
}
|
|
33781
|
+
/** `settings.json` holds unrelated user settings, so it must not be deleted. */
|
|
33782
|
+
isDeletable() {
|
|
33783
|
+
return false;
|
|
33784
|
+
}
|
|
33785
|
+
static getSettablePaths({ global = false } = {}) {
|
|
33786
|
+
return {
|
|
33787
|
+
relativeDirPath: global ? PI_AGENT_DIR_PATH : ".pi",
|
|
33788
|
+
relativeFilePath: PI_SETTINGS_FILE_NAME
|
|
33789
|
+
};
|
|
33790
|
+
}
|
|
33791
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
33792
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33793
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
33794
|
+
return new PiPermissions({
|
|
33795
|
+
outputRoot,
|
|
33796
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33797
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33798
|
+
fileContent,
|
|
33799
|
+
validate,
|
|
33800
|
+
global
|
|
33801
|
+
});
|
|
33802
|
+
}
|
|
33803
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
33804
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33805
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
33806
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
33807
|
+
const defaultTools = rulesyncPermissions.getJson().pi?.defaultTools;
|
|
33808
|
+
const patch = {};
|
|
33809
|
+
if (defaultTools !== void 0) patch[DEFAULT_TOOLS_KEY] = defaultTools;
|
|
33810
|
+
return new PiPermissions({
|
|
33811
|
+
outputRoot,
|
|
33812
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33813
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33814
|
+
fileContent: applySharedConfigPatch({
|
|
33815
|
+
fileKey: sharedConfigFileKey(paths),
|
|
33816
|
+
feature: "permissions",
|
|
33817
|
+
existingContent,
|
|
33818
|
+
patch,
|
|
33819
|
+
filePath
|
|
33820
|
+
}),
|
|
33821
|
+
validate: true,
|
|
33822
|
+
global
|
|
33823
|
+
});
|
|
33824
|
+
}
|
|
33825
|
+
toRulesyncPermissions() {
|
|
33826
|
+
const defaultTools = this.parseSettings()[DEFAULT_TOOLS_KEY];
|
|
33827
|
+
const config = { permission: {} };
|
|
33828
|
+
if (Array.isArray(defaultTools) && defaultTools.every((tool) => typeof tool === "string")) config.pi = { [DEFAULT_TOOLS_KEY]: defaultTools };
|
|
33829
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
33830
|
+
}
|
|
33831
|
+
parseSettings() {
|
|
33832
|
+
try {
|
|
33833
|
+
return parseSharedConfig({
|
|
33834
|
+
format: "json",
|
|
33835
|
+
fileContent: this.getFileContent()
|
|
33836
|
+
});
|
|
33837
|
+
} catch (error) {
|
|
33838
|
+
throw new Error(`Failed to parse Pi settings in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
33839
|
+
}
|
|
33840
|
+
}
|
|
33841
|
+
validate() {
|
|
33842
|
+
return {
|
|
33843
|
+
success: true,
|
|
33844
|
+
error: null
|
|
33845
|
+
};
|
|
33846
|
+
}
|
|
33847
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
33848
|
+
return new PiPermissions({
|
|
33849
|
+
outputRoot,
|
|
33850
|
+
relativeDirPath,
|
|
33851
|
+
relativeFilePath,
|
|
33852
|
+
fileContent: "{}",
|
|
33853
|
+
validate: false,
|
|
33854
|
+
global
|
|
33855
|
+
});
|
|
33856
|
+
}
|
|
33857
|
+
};
|
|
33858
|
+
//#endregion
|
|
33223
33859
|
//#region src/features/permissions/qwencode-permissions.ts
|
|
33224
33860
|
/**
|
|
33225
33861
|
* Qwen Code uses a settings.json file in `.qwen/` (project) or `~/.qwen/` (global).
|
|
@@ -34283,7 +34919,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
34283
34919
|
permission[category] ??= {};
|
|
34284
34920
|
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
34285
34921
|
}
|
|
34286
|
-
if (isStringArray$
|
|
34922
|
+
if (isStringArray$2(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
34287
34923
|
permission.read ??= {};
|
|
34288
34924
|
permission.read[path] ??= "allow";
|
|
34289
34925
|
}
|
|
@@ -35260,8 +35896,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
35260
35896
|
const executionProfiles = isRecord$1(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
35261
35897
|
const defaultProfile = executionProfiles && isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
35262
35898
|
const config = convertWarpToRulesyncPermissions({
|
|
35263
|
-
allow: defaultProfile ? isStringArray$
|
|
35264
|
-
deny: defaultProfile ? isStringArray$
|
|
35899
|
+
allow: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$2(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
35900
|
+
deny: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$2(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
35265
35901
|
});
|
|
35266
35902
|
const warpOverride = {};
|
|
35267
35903
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
@@ -35973,6 +36609,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
35973
36609
|
supportsImport: true
|
|
35974
36610
|
}
|
|
35975
36611
|
}],
|
|
36612
|
+
["pi", {
|
|
36613
|
+
class: PiPermissions,
|
|
36614
|
+
meta: {
|
|
36615
|
+
supportsProject: true,
|
|
36616
|
+
supportsGlobal: true,
|
|
36617
|
+
supportsImport: true
|
|
36618
|
+
}
|
|
36619
|
+
}],
|
|
35976
36620
|
["qwencode", {
|
|
35977
36621
|
class: QwencodePermissions,
|
|
35978
36622
|
meta: {
|
|
@@ -36775,11 +37419,12 @@ var AmpSkill = class AmpSkill extends ToolSkill {
|
|
|
36775
37419
|
};
|
|
36776
37420
|
}
|
|
36777
37421
|
toRulesyncSkill() {
|
|
36778
|
-
const
|
|
37422
|
+
const { name, description, ...ampSection } = this.getFrontmatter();
|
|
36779
37423
|
const rulesyncFrontmatter = {
|
|
36780
|
-
name
|
|
36781
|
-
description
|
|
36782
|
-
targets: ["*"]
|
|
37424
|
+
name,
|
|
37425
|
+
description,
|
|
37426
|
+
targets: ["*"],
|
|
37427
|
+
...Object.keys(ampSection).length > 0 && { amp: ampSection }
|
|
36783
37428
|
};
|
|
36784
37429
|
return new RulesyncSkill({
|
|
36785
37430
|
outputRoot: this.outputRoot,
|
|
@@ -36796,6 +37441,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
|
|
|
36796
37441
|
const settablePaths = AmpSkill.getSettablePaths({ global });
|
|
36797
37442
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
36798
37443
|
const ampFrontmatter = {
|
|
37444
|
+
...rulesyncFrontmatter.amp,
|
|
36799
37445
|
name: rulesyncFrontmatter.name,
|
|
36800
37446
|
description: rulesyncFrontmatter.description
|
|
36801
37447
|
};
|
|
@@ -38282,6 +38928,7 @@ const CursorSkillFrontmatterSchema = z.looseObject({
|
|
|
38282
38928
|
description: z.string(),
|
|
38283
38929
|
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
38284
38930
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
38931
|
+
"user-invocable": z.optional(z.boolean()),
|
|
38285
38932
|
metadata: z.optional(z.looseObject({}))
|
|
38286
38933
|
});
|
|
38287
38934
|
/**
|
|
@@ -38336,6 +38983,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38336
38983
|
const cursorSection = {
|
|
38337
38984
|
...frontmatter.paths !== void 0 && { paths: frontmatter.paths },
|
|
38338
38985
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
38986
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
38339
38987
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
38340
38988
|
};
|
|
38341
38989
|
const rulesyncFrontmatter = {
|
|
@@ -38363,11 +39011,16 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38363
39011
|
rootFrontmatter: rulesyncFrontmatter,
|
|
38364
39012
|
section: cursorSection
|
|
38365
39013
|
});
|
|
39014
|
+
const resolvedUserInvocable = resolveUserInvocable({
|
|
39015
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
39016
|
+
section: cursorSection
|
|
39017
|
+
});
|
|
38366
39018
|
const cursorFrontmatter = {
|
|
38367
39019
|
name: rulesyncFrontmatter.name,
|
|
38368
39020
|
description: rulesyncFrontmatter.description,
|
|
38369
39021
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
38370
39022
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
39023
|
+
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
38371
39024
|
...cursorSection?.metadata !== void 0 && { metadata: cursorSection.metadata }
|
|
38372
39025
|
};
|
|
38373
39026
|
return new CursorSkill({
|
|
@@ -39222,6 +39875,47 @@ const JunieSkillFrontmatterSchema = z.looseObject({
|
|
|
39222
39875
|
name: z.string(),
|
|
39223
39876
|
description: z.string()
|
|
39224
39877
|
});
|
|
39878
|
+
/** An ATX markdown heading line (`#` … `######`). */
|
|
39879
|
+
const HEADING_LINE = /^#{1,6}(\s|$)/;
|
|
39880
|
+
/**
|
|
39881
|
+
* Junie's own fallback for a `SKILL.md` with no `description`: "If
|
|
39882
|
+
* `description` is not provided in the frontmatter, Junie CLI extracts the
|
|
39883
|
+
* first paragraph of the body content as the description." Headings do not
|
|
39884
|
+
* count as that paragraph — "If the body is also empty or contains only
|
|
39885
|
+
* headings, the skill will fail to load."
|
|
39886
|
+
*
|
|
39887
|
+
* This is import-only. The canonical `RulesyncSkillFrontmatter` requires a
|
|
39888
|
+
* description, so without the fallback a skill Junie itself loads fine aborts
|
|
39889
|
+
* the whole import; generation keeps emitting an explicit description, which
|
|
39890
|
+
* the same docs recommend.
|
|
39891
|
+
*
|
|
39892
|
+
* Heading lines are therefore skipped rather than taken: a body opening with
|
|
39893
|
+
* `# Skill Name` would otherwise import that title as the description and —
|
|
39894
|
+
* because the next generate writes it out explicitly — replace Junie's own
|
|
39895
|
+
* correct fallback with the wrong value everywhere, canonical config included.
|
|
39896
|
+
*
|
|
39897
|
+
* A paragraph runs to the first blank line or heading, and is collapsed onto
|
|
39898
|
+
* one line because it becomes a YAML frontmatter value. A fenced code block is
|
|
39899
|
+
* not treated specially: it is ordinary content, so a body whose first
|
|
39900
|
+
* paragraph is a fence yields the fence text. Returns an empty string when the
|
|
39901
|
+
* body holds no such paragraph, which the caller turns into a skipped skill.
|
|
39902
|
+
*
|
|
39903
|
+
* @see https://junie.jetbrains.com/docs/agent-skills.html
|
|
39904
|
+
*/
|
|
39905
|
+
function deriveDescriptionFromBody(body) {
|
|
39906
|
+
const paragraph = [];
|
|
39907
|
+
for (const rawLine of body.split(/\r?\n/)) {
|
|
39908
|
+
const line = rawLine.trim();
|
|
39909
|
+
if (paragraph.length === 0) {
|
|
39910
|
+
if (line === "" || HEADING_LINE.test(line)) continue;
|
|
39911
|
+
paragraph.push(line);
|
|
39912
|
+
continue;
|
|
39913
|
+
}
|
|
39914
|
+
if (line === "" || HEADING_LINE.test(line)) break;
|
|
39915
|
+
paragraph.push(line);
|
|
39916
|
+
}
|
|
39917
|
+
return paragraph.join(" ").trim();
|
|
39918
|
+
}
|
|
39225
39919
|
/**
|
|
39226
39920
|
* Represents a JetBrains Junie skill directory.
|
|
39227
39921
|
* Skills are stored under the .junie/skills directory with SKILL.md files.
|
|
@@ -39318,7 +40012,16 @@ var JunieSkill = class JunieSkill extends ToolSkill {
|
|
|
39318
40012
|
...params,
|
|
39319
40013
|
getSettablePaths: JunieSkill.getSettablePaths
|
|
39320
40014
|
});
|
|
39321
|
-
|
|
40015
|
+
let frontmatter = loaded.frontmatter;
|
|
40016
|
+
if (isRecord$1(frontmatter) && frontmatter.description === void 0) {
|
|
40017
|
+
const derived = deriveDescriptionFromBody(loaded.body);
|
|
40018
|
+
if (derived === "") throw new Error(`Cannot import ${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.`);
|
|
40019
|
+
frontmatter = {
|
|
40020
|
+
...frontmatter,
|
|
40021
|
+
description: derived
|
|
40022
|
+
};
|
|
40023
|
+
}
|
|
40024
|
+
const result = JunieSkillFrontmatterSchema.safeParse(frontmatter);
|
|
39322
40025
|
if (!result.success) {
|
|
39323
40026
|
const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
39324
40027
|
throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
@@ -41783,7 +42486,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
41783
42486
|
meta: {
|
|
41784
42487
|
supportsProject: true,
|
|
41785
42488
|
supportsSimulated: false,
|
|
41786
|
-
supportsGlobal: true
|
|
42489
|
+
supportsGlobal: true,
|
|
42490
|
+
lenientImport: true
|
|
41787
42491
|
}
|
|
41788
42492
|
}],
|
|
41789
42493
|
["kilo", {
|
|
@@ -46319,6 +47023,23 @@ var TaktSubagent = class TaktSubagent extends ToolSubagent {
|
|
|
46319
47023
|
};
|
|
46320
47024
|
//#endregion
|
|
46321
47025
|
//#region src/features/subagents/vibe-subagent.ts
|
|
47026
|
+
/**
|
|
47027
|
+
* Where Vibe reads a custom system prompt from: `.vibe/prompts/<id>.md`
|
|
47028
|
+
* (project) and `~/.vibe/prompts/<id>.md` (user), with the custom directories
|
|
47029
|
+
* winning over the builtins. The id must be a bare file name with no path
|
|
47030
|
+
* separators.
|
|
47031
|
+
* @see vibe/core/prompts/__init__.py
|
|
47032
|
+
*/
|
|
47033
|
+
const VIBE_PROMPTS_DIR_PATH = join(".vibe", "prompts");
|
|
47034
|
+
/**
|
|
47035
|
+
* Vibe resolves a `system_prompt_id` by file name, and
|
|
47036
|
+
* `VibeConfigSchema._check_system_prompt` evaluates it during validation — an
|
|
47037
|
+
* unresolvable id raises and the agent is dropped at discovery. The id must
|
|
47038
|
+
* therefore stay a bare slug with no path separators.
|
|
47039
|
+
*/
|
|
47040
|
+
function promptSlug(relativeFilePath) {
|
|
47041
|
+
return basename(relativeFilePath).replace(/\.(md|toml)$/, "").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
47042
|
+
}
|
|
46322
47043
|
const VibeSubagentTomlSchema = z.looseObject({
|
|
46323
47044
|
agent_type: z.enum(["agent", "subagent"]),
|
|
46324
47045
|
display_name: z.optional(z.string()),
|
|
@@ -46335,14 +47056,22 @@ const VibeSubagentTomlSchema = z.looseObject({
|
|
|
46335
47056
|
});
|
|
46336
47057
|
var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
46337
47058
|
body;
|
|
46338
|
-
|
|
46339
|
-
|
|
47059
|
+
promptFile;
|
|
47060
|
+
resolvedSystemPrompt;
|
|
47061
|
+
constructor({ body, promptFile = false, resolvedSystemPrompt, ...rest }) {
|
|
47062
|
+
if (rest.validate !== false && !promptFile) try {
|
|
46340
47063
|
VibeSubagentTomlSchema.parse(smolToml.parse(body));
|
|
46341
47064
|
} catch (error) {
|
|
46342
47065
|
throw new Error(`Invalid TOML in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
46343
47066
|
}
|
|
46344
47067
|
super({ ...rest });
|
|
46345
47068
|
this.body = body;
|
|
47069
|
+
this.promptFile = promptFile;
|
|
47070
|
+
this.resolvedSystemPrompt = resolvedSystemPrompt;
|
|
47071
|
+
}
|
|
47072
|
+
/** True for the companion `.vibe/prompts/<id>.md` file. */
|
|
47073
|
+
isPromptFile() {
|
|
47074
|
+
return this.promptFile;
|
|
46346
47075
|
}
|
|
46347
47076
|
static getSettablePaths(_options = {}) {
|
|
46348
47077
|
return { relativeDirPath: join(".vibe", "agents") };
|
|
@@ -46357,7 +47086,9 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46357
47086
|
} catch (error) {
|
|
46358
47087
|
throw new Error(`Failed to parse TOML in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
46359
47088
|
}
|
|
46360
|
-
const { system_prompt, description, display_name, ...vibeSection } = parsed;
|
|
47089
|
+
const { system_prompt, system_prompt_id, description, display_name, ...vibeSection } = parsed;
|
|
47090
|
+
const resolvedBody = this.resolvedSystemPrompt ?? system_prompt;
|
|
47091
|
+
const unresolvedPromptId = this.resolvedSystemPrompt === void 0 ? system_prompt_id : void 0;
|
|
46361
47092
|
const fileStem = basename(this.getRelativeFilePath(), ".toml");
|
|
46362
47093
|
const rulesyncFrontmatter = {
|
|
46363
47094
|
targets: ["vibe"],
|
|
@@ -46366,13 +47097,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46366
47097
|
vibe: {
|
|
46367
47098
|
...display_name !== void 0 && { display_name },
|
|
46368
47099
|
...description !== void 0 && { description },
|
|
47100
|
+
...unresolvedPromptId !== void 0 && { system_prompt_id: unresolvedPromptId },
|
|
46369
47101
|
...vibeSection
|
|
46370
47102
|
}
|
|
46371
47103
|
};
|
|
46372
47104
|
return new RulesyncSubagent({
|
|
46373
47105
|
outputRoot: this.outputRoot,
|
|
46374
47106
|
frontmatter: rulesyncFrontmatter,
|
|
46375
|
-
body:
|
|
47107
|
+
body: resolvedBody ?? "",
|
|
46376
47108
|
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
46377
47109
|
relativeFilePath: this.getRelativeFilePath().replace(/\.toml$/, ".md"),
|
|
46378
47110
|
validate: true
|
|
@@ -46387,12 +47119,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46387
47119
|
"description",
|
|
46388
47120
|
"system_prompt"
|
|
46389
47121
|
]);
|
|
47122
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47123
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
46390
47124
|
const tomlObj = {
|
|
46391
47125
|
agent_type: rawSection.agent_type === "agent" ? "agent" : "subagent",
|
|
46392
47126
|
display_name: typeof rawSection.display_name === "string" ? rawSection.display_name : frontmatter.name,
|
|
46393
47127
|
...frontmatter.description ? { description: frontmatter.description } : {},
|
|
46394
|
-
...
|
|
46395
|
-
...
|
|
47128
|
+
...vibeSection,
|
|
47129
|
+
...promptBody ? { system_prompt_id: slug } : {}
|
|
46396
47130
|
};
|
|
46397
47131
|
const body = smolToml.stringify(tomlObj);
|
|
46398
47132
|
const paths = this.getSettablePaths({ global });
|
|
@@ -46407,6 +47141,36 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46407
47141
|
global
|
|
46408
47142
|
});
|
|
46409
47143
|
}
|
|
47144
|
+
/**
|
|
47145
|
+
* Emits the agent TOML plus its companion prompt file. The prompt file MUST
|
|
47146
|
+
* be written together with the id: `VibeConfigSchema._check_system_prompt`
|
|
47147
|
+
* evaluates the property during validation, so an unresolvable
|
|
47148
|
+
* `system_prompt_id` raises and `AgentRegistry._try_load` drops the agent
|
|
47149
|
+
* with a warning.
|
|
47150
|
+
*/
|
|
47151
|
+
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false }) {
|
|
47152
|
+
return rulesyncSubagents.flatMap((rulesyncSubagent) => {
|
|
47153
|
+
const agentFile = VibeSubagent.fromRulesyncSubagent({
|
|
47154
|
+
outputRoot,
|
|
47155
|
+
relativeDirPath: VibeSubagent.getSettablePaths({ global }).relativeDirPath,
|
|
47156
|
+
rulesyncSubagent,
|
|
47157
|
+
global
|
|
47158
|
+
});
|
|
47159
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47160
|
+
if (!promptBody) return [agentFile];
|
|
47161
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
47162
|
+
return [agentFile, new VibeSubagent({
|
|
47163
|
+
outputRoot,
|
|
47164
|
+
body: promptBody,
|
|
47165
|
+
relativeDirPath: VIBE_PROMPTS_DIR_PATH,
|
|
47166
|
+
relativeFilePath: `${slug}.md`,
|
|
47167
|
+
fileContent: promptBody,
|
|
47168
|
+
promptFile: true,
|
|
47169
|
+
validate: false,
|
|
47170
|
+
global
|
|
47171
|
+
})];
|
|
47172
|
+
});
|
|
47173
|
+
}
|
|
46410
47174
|
validate() {
|
|
46411
47175
|
try {
|
|
46412
47176
|
VibeSubagentTomlSchema.parse(smolToml.parse(this.body));
|
|
@@ -46431,6 +47195,10 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46431
47195
|
const paths = this.getSettablePaths({ global });
|
|
46432
47196
|
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
46433
47197
|
const fileContent = await readFileContent(filePath);
|
|
47198
|
+
const resolvedSystemPrompt = await VibeSubagent.readSystemPrompt({
|
|
47199
|
+
outputRoot,
|
|
47200
|
+
fileContent
|
|
47201
|
+
});
|
|
46434
47202
|
const subagent = new VibeSubagent({
|
|
46435
47203
|
outputRoot,
|
|
46436
47204
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -46438,7 +47206,8 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46438
47206
|
body: fileContent.trim(),
|
|
46439
47207
|
fileContent,
|
|
46440
47208
|
validate,
|
|
46441
|
-
global
|
|
47209
|
+
global,
|
|
47210
|
+
...resolvedSystemPrompt !== void 0 && { resolvedSystemPrompt }
|
|
46442
47211
|
});
|
|
46443
47212
|
if (validate) {
|
|
46444
47213
|
const result = subagent.validate();
|
|
@@ -46446,6 +47215,21 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46446
47215
|
}
|
|
46447
47216
|
return subagent;
|
|
46448
47217
|
}
|
|
47218
|
+
/**
|
|
47219
|
+
* Read `.vibe/prompts/<id>.md` for an agent that names a `system_prompt_id`.
|
|
47220
|
+
* Returns `undefined` when there is no id or the file is missing, in which
|
|
47221
|
+
* case import falls back to the legacy `system_prompt` key.
|
|
47222
|
+
*/
|
|
47223
|
+
static async readSystemPrompt({ outputRoot, fileContent }) {
|
|
47224
|
+
let promptId;
|
|
47225
|
+
try {
|
|
47226
|
+
promptId = VibeSubagentTomlSchema.parse(smolToml.parse(fileContent)).system_prompt_id;
|
|
47227
|
+
} catch {
|
|
47228
|
+
return;
|
|
47229
|
+
}
|
|
47230
|
+
if (typeof promptId !== "string" || promptId === "" || /[\\/]/.test(promptId)) return;
|
|
47231
|
+
return await readFileContentOrNull(join(outputRoot, VIBE_PROMPTS_DIR_PATH, `${promptId}.md`)) ?? void 0;
|
|
47232
|
+
}
|
|
46449
47233
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
46450
47234
|
return new VibeSubagent({
|
|
46451
47235
|
outputRoot,
|
|
@@ -46488,7 +47272,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46488
47272
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
46489
47273
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
46490
47274
|
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
46491
|
-
if (isStringArray$
|
|
47275
|
+
if (isStringArray$2(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
46492
47276
|
return mode;
|
|
46493
47277
|
}
|
|
46494
47278
|
toRulesyncSubagents() {
|
|
@@ -46500,7 +47284,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46500
47284
|
targets: ["zoocode"],
|
|
46501
47285
|
roo: restRooSection
|
|
46502
47286
|
};
|
|
46503
|
-
if (isStringArray$
|
|
47287
|
+
if (isStringArray$2(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
46504
47288
|
return new RulesyncSubagent({
|
|
46505
47289
|
outputRoot: ".",
|
|
46506
47290
|
frontmatter: rebuilt,
|
|
@@ -49403,8 +50187,13 @@ const STRATEGIES = [
|
|
|
49403
50187
|
* file per rule under `.devin/rules/*.md` (Devin Desktop Cascade) with YAML
|
|
49404
50188
|
* frontmatter carrying a `trigger` (always_on | glob | manual | model_decision)
|
|
49405
50189
|
* plus companion `globs`/`description` fields.
|
|
49406
|
-
* - Global scope:
|
|
49407
|
-
*
|
|
50190
|
+
* - Global scope: mirrors the project layout. The root rule is a single
|
|
50191
|
+
* plain-markdown, always-on file (no frontmatter) at
|
|
50192
|
+
* `~/.config/devin/AGENTS.md`; non-root rules are one file per rule under
|
|
50193
|
+
* `~/.devin/rules/*.md` with the same `trigger`/`globs` frontmatter as project
|
|
50194
|
+
* scope. Note the directory split — the per-rule global directory is the home
|
|
50195
|
+
* `~/.devin/`, not the `~/.config/devin/` tree every other Devin global
|
|
50196
|
+
* surface uses. That is what the rules page documents.
|
|
49408
50197
|
*
|
|
49409
50198
|
* Trigger inference (when no explicit devin trigger is persisted):
|
|
49410
50199
|
* - Specific globs (non wildcard) → glob
|
|
@@ -49435,19 +50224,32 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49435
50224
|
relativeFilePath: DEVIN_GLOBAL_AGENTS_FILE_NAME
|
|
49436
50225
|
};
|
|
49437
50226
|
}
|
|
50227
|
+
/**
|
|
50228
|
+
* The always-on root rule file for a scope: the project-root `AGENTS.md`
|
|
50229
|
+
* Devin CLI / Devin Local reads, or `~/.config/devin/AGENTS.md` in global
|
|
50230
|
+
* scope. Single-sourced so `getSettablePaths`, `fromFile` and
|
|
50231
|
+
* `fromRulesyncRule` cannot disagree about it.
|
|
50232
|
+
*/
|
|
50233
|
+
static getRootPath(global, excludeToolDir) {
|
|
50234
|
+
return global ? DevinRule.getGlobalRootPath(excludeToolDir) : {
|
|
50235
|
+
relativeDirPath: ".",
|
|
50236
|
+
relativeFilePath: "AGENTS.md"
|
|
50237
|
+
};
|
|
50238
|
+
}
|
|
49438
50239
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
49439
|
-
if (global) return {
|
|
50240
|
+
if (global) return {
|
|
50241
|
+
root: DevinRule.getRootPath(true, excludeToolDir),
|
|
50242
|
+
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
50243
|
+
};
|
|
49440
50244
|
return {
|
|
49441
|
-
root:
|
|
49442
|
-
relativeDirPath: ".",
|
|
49443
|
-
relativeFilePath: "AGENTS.md"
|
|
49444
|
-
},
|
|
50245
|
+
root: DevinRule.getRootPath(false, excludeToolDir),
|
|
49445
50246
|
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
49446
50247
|
};
|
|
49447
50248
|
}
|
|
49448
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
49449
|
-
|
|
49450
|
-
|
|
50249
|
+
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
50250
|
+
const nonRootDirPath = buildToolPath(DEVIN_DIR, "rules");
|
|
50251
|
+
if (relativeDirPath !== void 0 ? relativeDirPath !== nonRootDirPath : relativeFilePath === DevinRule.getRootPath(global).relativeFilePath) {
|
|
50252
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49451
50253
|
const fileContent = await readFileContent(join(outputRoot, rootPath.relativeDirPath, rootPath.relativeFilePath));
|
|
49452
50254
|
return new DevinRule({
|
|
49453
50255
|
outputRoot,
|
|
@@ -49459,19 +50261,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49459
50261
|
root: true
|
|
49460
50262
|
});
|
|
49461
50263
|
}
|
|
49462
|
-
if (relativeFilePath === "AGENTS.md") {
|
|
49463
|
-
const fileContent = await readFileContent(join(outputRoot, relativeFilePath));
|
|
49464
|
-
return new DevinRule({
|
|
49465
|
-
outputRoot,
|
|
49466
|
-
relativeDirPath: ".",
|
|
49467
|
-
relativeFilePath: "AGENTS.md",
|
|
49468
|
-
frontmatter: {},
|
|
49469
|
-
body: fileContent,
|
|
49470
|
-
validate,
|
|
49471
|
-
root: true
|
|
49472
|
-
});
|
|
49473
|
-
}
|
|
49474
|
-
const nonRootDirPath = buildToolPath(DEVIN_DIR, "rules");
|
|
49475
50264
|
const filePath = join(outputRoot, nonRootDirPath, relativeFilePath);
|
|
49476
50265
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
49477
50266
|
let parsedFrontmatter;
|
|
@@ -49491,8 +50280,9 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49491
50280
|
});
|
|
49492
50281
|
}
|
|
49493
50282
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
49494
|
-
|
|
49495
|
-
|
|
50283
|
+
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
50284
|
+
if (rulesyncFrontmatter.root) {
|
|
50285
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49496
50286
|
return new DevinRule({
|
|
49497
50287
|
outputRoot,
|
|
49498
50288
|
relativeDirPath: rootPath.relativeDirPath,
|
|
@@ -49503,16 +50293,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49503
50293
|
root: true
|
|
49504
50294
|
});
|
|
49505
50295
|
}
|
|
49506
|
-
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
49507
|
-
if (rulesyncFrontmatter.root) return new DevinRule({
|
|
49508
|
-
outputRoot,
|
|
49509
|
-
relativeDirPath: ".",
|
|
49510
|
-
relativeFilePath: "AGENTS.md",
|
|
49511
|
-
frontmatter: {},
|
|
49512
|
-
body: rulesyncRule.getBody(),
|
|
49513
|
-
validate,
|
|
49514
|
-
root: true
|
|
49515
|
-
});
|
|
49516
50296
|
const storedDevin = rulesyncFrontmatter.devin;
|
|
49517
50297
|
const normalized = normalizeStoredDevin(storedDevin);
|
|
49518
50298
|
const storedTrigger = storedDevin?.trigger;
|
|
@@ -49572,7 +50352,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49572
50352
|
error: null
|
|
49573
50353
|
};
|
|
49574
50354
|
}
|
|
49575
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath
|
|
50355
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
49576
50356
|
return new DevinRule({
|
|
49577
50357
|
outputRoot,
|
|
49578
50358
|
relativeDirPath,
|
|
@@ -49580,7 +50360,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49580
50360
|
frontmatter: {},
|
|
49581
50361
|
body: "",
|
|
49582
50362
|
validate: false,
|
|
49583
|
-
root:
|
|
50363
|
+
root: relativeDirPath !== buildToolPath(DEVIN_DIR, "rules")
|
|
49584
50364
|
});
|
|
49585
50365
|
}
|
|
49586
50366
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -50213,6 +50993,11 @@ var KimiCodeRule = class KimiCodeRule extends ToolRule {
|
|
|
50213
50993
|
};
|
|
50214
50994
|
//#endregion
|
|
50215
50995
|
//#region src/features/rules/kiro-rule.ts
|
|
50996
|
+
/**
|
|
50997
|
+
* Kiro loads `AGENTS.md` as steering context from any directory in the
|
|
50998
|
+
* workspace tree (CLI 2.18.0 / IDE 1.0.309), not just the workspace root.
|
|
50999
|
+
*/
|
|
51000
|
+
const KIRO_NESTED_STEERING_FILE_NAME = "AGENTS.md";
|
|
50216
51001
|
const WILDCARD_GLOBS = /* @__PURE__ */ new Set([
|
|
50217
51002
|
"**/*",
|
|
50218
51003
|
"**",
|
|
@@ -50286,8 +51071,17 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50286
51071
|
};
|
|
50287
51072
|
return { nonRoot: { relativeDirPath: steeringDirPath } };
|
|
50288
51073
|
}
|
|
50289
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
51074
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
50290
51075
|
const paths = this.getSettablePaths({ global });
|
|
51076
|
+
const steeringDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
51077
|
+
if (overrideDirPath !== void 0 && overrideDirPath !== steeringDirPath && overrideDirPath !== "." && relativeFilePath === KIRO_NESTED_STEERING_FILE_NAME) return new KiroRule({
|
|
51078
|
+
outputRoot,
|
|
51079
|
+
relativeDirPath: overrideDirPath,
|
|
51080
|
+
relativeFilePath,
|
|
51081
|
+
fileContent: await readFileContent(join(outputRoot, overrideDirPath, relativeFilePath)),
|
|
51082
|
+
validate,
|
|
51083
|
+
root: false
|
|
51084
|
+
});
|
|
50291
51085
|
const isRoot = "root" in paths && relativeFilePath === paths.root.relativeFilePath;
|
|
50292
51086
|
const relativeDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
50293
51087
|
const fileContent = await readFileContent(join(outputRoot, relativeDirPath, relativeFilePath));
|
|
@@ -50311,6 +51105,12 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50311
51105
|
});
|
|
50312
51106
|
if (params.root) return new KiroRule(params);
|
|
50313
51107
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
51108
|
+
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
51109
|
+
if (!global && subprojectPath) return new KiroRule({
|
|
51110
|
+
...params,
|
|
51111
|
+
relativeDirPath: join(subprojectPath),
|
|
51112
|
+
relativeFilePath: KIRO_NESTED_STEERING_FILE_NAME
|
|
51113
|
+
});
|
|
50314
51114
|
const inclusion = deriveKiroInclusion({
|
|
50315
51115
|
kiro: frontmatter.kiro,
|
|
50316
51116
|
globs: frontmatter.globs
|
|
@@ -50321,7 +51121,34 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50321
51121
|
fileContent: stringifyFrontmatter(rulesyncRule.getBody(), inclusion)
|
|
50322
51122
|
});
|
|
50323
51123
|
}
|
|
51124
|
+
/**
|
|
51125
|
+
* The subproject directory a nested `AGENTS.md` scopes, or `undefined` for the
|
|
51126
|
+
* root rule and for the `.kiro/steering/` files.
|
|
51127
|
+
*/
|
|
51128
|
+
getSubprojectPath() {
|
|
51129
|
+
if (this.isRoot() || this.getRelativeFilePath() !== KIRO_NESTED_STEERING_FILE_NAME) return;
|
|
51130
|
+
const relativeDirPath = toPosixPath(this.getRelativeDirPath());
|
|
51131
|
+
if (relativeDirPath === "." || relativeDirPath === "" || relativeDirPath.startsWith(".")) return;
|
|
51132
|
+
return relativeDirPath;
|
|
51133
|
+
}
|
|
50324
51134
|
toRulesyncRule() {
|
|
51135
|
+
const subprojectPath = this.getSubprojectPath();
|
|
51136
|
+
if (subprojectPath !== void 0) {
|
|
51137
|
+
const slug = subprojectPath.replaceAll("/", "-");
|
|
51138
|
+
return new RulesyncRule({
|
|
51139
|
+
outputRoot: this.getOutputRoot(),
|
|
51140
|
+
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
51141
|
+
relativeFilePath: `${slug}-kiro.md`,
|
|
51142
|
+
frontmatter: {
|
|
51143
|
+
targets: ["kiro"],
|
|
51144
|
+
root: false,
|
|
51145
|
+
globs: [`${subprojectPath}/**/*`],
|
|
51146
|
+
agentsmd: { subprojectPath }
|
|
51147
|
+
},
|
|
51148
|
+
body: this.getFileContent(),
|
|
51149
|
+
validate: true
|
|
51150
|
+
});
|
|
51151
|
+
}
|
|
50325
51152
|
const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getRelativeFilePath());
|
|
50326
51153
|
const inclusion = typeof frontmatter.inclusion === "string" ? frontmatter.inclusion : void 0;
|
|
50327
51154
|
if (!inclusion) return this.toRulesyncRuleDefault();
|
|
@@ -50354,6 +51181,26 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50354
51181
|
error: null
|
|
50355
51182
|
};
|
|
50356
51183
|
}
|
|
51184
|
+
/**
|
|
51185
|
+
* Nested `AGENTS.md` files anywhere in the workspace tree, which Kiro loads as
|
|
51186
|
+
* steering context alongside `.kiro/steering/`. Import-only, like every other
|
|
51187
|
+
* nested scan: these are hand-authored files outside a rulesync-owned
|
|
51188
|
+
* directory, so enumerating them for `--delete` would sweep away work rulesync
|
|
51189
|
+
* never wrote.
|
|
51190
|
+
* @see https://kiro.dev/docs/steering/
|
|
51191
|
+
*/
|
|
51192
|
+
static getNestedFilePatterns({ outputRoot }) {
|
|
51193
|
+
const root = toPosixPath(outputRoot);
|
|
51194
|
+
return {
|
|
51195
|
+
include: [`${root}/**/${KIRO_NESTED_STEERING_FILE_NAME}`],
|
|
51196
|
+
ignore: [
|
|
51197
|
+
`${root}/${KIRO_NESTED_STEERING_FILE_NAME}`,
|
|
51198
|
+
`${root}/**/.*/**`,
|
|
51199
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
51200
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
51201
|
+
]
|
|
51202
|
+
};
|
|
51203
|
+
}
|
|
50357
51204
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
50358
51205
|
return new KiroRule({
|
|
50359
51206
|
outputRoot,
|
|
@@ -51822,6 +52669,18 @@ const resolveIncludeLocalRoot = (options) => {
|
|
|
51822
52669
|
return parsed.data.includeLocalRoot ?? true;
|
|
51823
52670
|
};
|
|
51824
52671
|
/**
|
|
52672
|
+
* Whether `candidate` is `base` itself or a class that extends it.
|
|
52673
|
+
*
|
|
52674
|
+
* Class-identity dispatch (`factory.class === Base`) is subclass-blind: a target
|
|
52675
|
+
* whose adapter merely narrows another one — `ZoocodeRule extends RooRule` — fails
|
|
52676
|
+
* a strict `===` and silently falls through. That is how `zoocode` ended up
|
|
52677
|
+
* declaring `localRootMode: "separate-local-file"` while never emitting
|
|
52678
|
+
* `AGENTS.local.md`, in both directions and without a warning.
|
|
52679
|
+
*/
|
|
52680
|
+
function isClassOrSubclassOf({ candidate, base }) {
|
|
52681
|
+
return candidate === base || Object.prototype.isPrototypeOf.call(base, candidate);
|
|
52682
|
+
}
|
|
52683
|
+
/**
|
|
51825
52684
|
* Factory Map mapping tool targets to their rule factories.
|
|
51826
52685
|
* Using Map to preserve insertion order for consistent iteration.
|
|
51827
52686
|
*/
|
|
@@ -52175,7 +53034,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
52175
53034
|
meta: {
|
|
52176
53035
|
extension: "md",
|
|
52177
53036
|
supportsGlobal: true,
|
|
52178
|
-
ruleDiscoveryMode: "auto"
|
|
53037
|
+
ruleDiscoveryMode: "auto",
|
|
53038
|
+
localRootMode: "separate-local-file",
|
|
53039
|
+
localRootFileName: "AGENTS.local.md"
|
|
52179
53040
|
}
|
|
52180
53041
|
}],
|
|
52181
53042
|
["zed", {
|
|
@@ -52462,7 +53323,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52462
53323
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
52463
53324
|
}
|
|
52464
53325
|
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
52465
|
-
if (
|
|
53326
|
+
if (isClassOrSubclassOf({
|
|
53327
|
+
candidate: factory.class,
|
|
53328
|
+
base: ClaudecodeRule
|
|
53329
|
+
})) {
|
|
52466
53330
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
52467
53331
|
return new ClaudecodeRule({
|
|
52468
53332
|
outputRoot: this.outputRoot,
|
|
@@ -52475,7 +53339,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52475
53339
|
localRoot
|
|
52476
53340
|
});
|
|
52477
53341
|
}
|
|
52478
|
-
if (
|
|
53342
|
+
if (isClassOrSubclassOf({
|
|
53343
|
+
candidate: factory.class,
|
|
53344
|
+
base: ClaudecodeLegacyRule
|
|
53345
|
+
})) {
|
|
52479
53346
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
52480
53347
|
return new ClaudecodeLegacyRule({
|
|
52481
53348
|
outputRoot: this.outputRoot,
|
|
@@ -52487,7 +53354,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52487
53354
|
localRoot
|
|
52488
53355
|
});
|
|
52489
53356
|
}
|
|
52490
|
-
if (
|
|
53357
|
+
if (isClassOrSubclassOf({
|
|
53358
|
+
candidate: factory.class,
|
|
53359
|
+
base: RovodevRule
|
|
53360
|
+
})) return new RovodevRule({
|
|
52491
53361
|
outputRoot: this.outputRoot,
|
|
52492
53362
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52493
53363
|
relativeFilePath: fileName,
|
|
@@ -52496,7 +53366,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52496
53366
|
root: true,
|
|
52497
53367
|
localRoot
|
|
52498
53368
|
});
|
|
52499
|
-
if (
|
|
53369
|
+
if (isClassOrSubclassOf({
|
|
53370
|
+
candidate: factory.class,
|
|
53371
|
+
base: RooRule
|
|
53372
|
+
})) return new RooRule({
|
|
52500
53373
|
outputRoot: this.outputRoot,
|
|
52501
53374
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52502
53375
|
relativeFilePath: fileName,
|
|
@@ -52505,7 +53378,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52505
53378
|
root: true,
|
|
52506
53379
|
localRoot
|
|
52507
53380
|
});
|
|
52508
|
-
if (
|
|
53381
|
+
if (isClassOrSubclassOf({
|
|
53382
|
+
candidate: factory.class,
|
|
53383
|
+
base: DevinRule
|
|
53384
|
+
})) return new DevinRule({
|
|
53385
|
+
outputRoot: this.outputRoot,
|
|
53386
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
53387
|
+
relativeFilePath: fileName,
|
|
53388
|
+
frontmatter: {},
|
|
53389
|
+
body,
|
|
53390
|
+
validate: true,
|
|
53391
|
+
root: true,
|
|
53392
|
+
localRoot
|
|
53393
|
+
});
|
|
53394
|
+
if (isClassOrSubclassOf({
|
|
53395
|
+
candidate: factory.class,
|
|
53396
|
+
base: QwencodeRule
|
|
53397
|
+
})) return new QwencodeRule({
|
|
52509
53398
|
outputRoot: this.outputRoot,
|
|
52510
53399
|
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
52511
53400
|
relativeFilePath: fileName,
|
|
@@ -54722,4 +55611,4 @@ async function importChecksCore(params) {
|
|
|
54722
55611
|
//#endregion
|
|
54723
55612
|
export { JsonLogger as $, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as $t, RulesyncRuleFrontmatterSchema as A, ALL_TOOL_TARGETS_WITH_WILDCARD as At, RulesyncCheck as B, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Bt, CODEXCLI_DIR as C, removeTempDirectory as Ct, RulesyncSkill as D, writeFileBuffer as Dt, RulesyncSubagentFrontmatterSchema as E, toPosixPath as Et, getRulesyncSourceCandidates as F, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Ft, SKILL_FILE_NAME as G, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Gt, stringifyFrontmatter as H, RULESYNC_HOOKS_FILE_NAME as Ht, resolveRulesyncSourceWritePath as I, RULESYNC_CHECKS_RELATIVE_DIR_PATH as It, ConfigFileSchema as J, RULESYNC_MCP_LEGACY_FILE_NAME as Jt, ConfigResolver as K, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Kt, parseJsonc as L, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Lt, RulesyncMcp as M, ToolTargetSchema as Mt, RulesyncIgnore as N, MAX_FILE_SIZE as Nt, RulesyncSkillFrontmatterSchema as O, writeFileContent as Ot, RulesyncHooks as P, RULESYNC_AIIGNORE_FILE_NAME as Pt, ConsoleLogger as Q, RULESYNC_PERMISSIONS_FILE_NAME as Qt, RulesyncCommand as R, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Rt, CODEXCLI_BASH_RULES_FILE_NAME as S, removeFileStrict as St, RulesyncSubagent as T, runWithDirectoryRollback as Tt, loadYaml as U, RULESYNC_HOOKS_LEGACY_FILE_NAME as Ut, RulesyncCheckFrontmatterSchema as V, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Vt, SHARED_USER_MANAGED_CONFIG_PATHS as W, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Wt, SourceEntrySchema as X, RULESYNC_MCP_SCHEMA_URL as Xt, GITIGNORE_DESTINATION_KEY as Y, RULESYNC_MCP_RELATIVE_FILE_PATH as Yt, findControlCharacter as Z, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Zt, CLAUDECODE_LOCAL_RULE_FILE_NAME as _, readFileContent as _t, convertFromTool as a, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as an, assertTreeContainsNoSymlinks as at, CLAUDECODE_SKILLS_DIR_PATH as b, removeDirectoryStrict as bt, SubagentsProcessor as c, ALL_FEATURES_WITH_WILDCARD as cn, createTempDirectory as ct, IgnoreProcessor as d, fileExists as dt, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as en, fallbackLogger as et, HooksProcessor as f, findFilesByGlobs as ft, CLAUDECODE_DIR as g, listDirectoryFiles as gt, QWENCODE_LOCAL_RULE_FILE_NAME as h, isSymlink as ht, getProcessorRegistryEntry as i, RULESYNC_SKILLS_RELATIVE_DIR_PATH as in, assertDirectoryIfExists as it, RulesyncPermissions as j, PACKAGING_TOOL_TARGETS as jt, RulesyncRule as k, ALL_TOOL_TARGETS as kt, SkillsProcessor as l, DEPRECATED_FEATURE_REPLACEMENTS as ln, directoryExists as lt, QWENCODE_DIR as m, getHomeDirectory as mt, checkRulesyncDirExists as n, RULESYNC_RELATIVE_DIR_PATH as nn, CLIError as nt, isPackagingToolTarget as o, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as on, assertWritablePathInsideRoot as ot, CommandsProcessor as p, getFileSize as pt, CONFLICTING_TARGET_PAIRS as q, RULESYNC_MCP_FILE_NAME as qt, generate as r, RULESYNC_RULES_RELATIVE_DIR_PATH as rn, ErrorCodes as rt, RulesProcessor as s, ALL_FEATURES as sn, checkPathTraversal as st, importFromTool as t, RULESYNC_PERMISSIONS_SCHEMA_URL as tn, warnOnConflictingFlags as tt, McpProcessor as u, formatError as un, ensureDir as ut, CLAUDECODE_MEMORIES_DIR_NAME as v, readFileContentOrNull as vt, getLocalSkillDirNames as w, resolvePath as wt, ChecksProcessor as x, removeFile as xt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as y, removeDirectory as yt, RulesyncCommandFrontmatterSchema as z, RULESYNC_CONFIG_SCHEMA_URL as zt };
|
|
54724
55613
|
|
|
54725
|
-
//# sourceMappingURL=import-
|
|
55614
|
+
//# sourceMappingURL=import-Dswkc7Ub.js.map
|