rulesync 16.12.0 → 16.14.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 +2 -2
- package/dist/cli/index.cjs +9 -4
- package/dist/cli/index.js +9 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-zGCKgpdt.js → import-Ds0QeVp6.js} +1125 -153
- package/dist/import-Ds0QeVp6.js.map +1 -0
- package/dist/{import-BwdPcfzS.cjs → import-DsiSzsJH.cjs} +1139 -167
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +13 -13
- package/dist/import-zGCKgpdt.js.map +0 -1
|
@@ -134,27 +134,27 @@ const isFeatureValueEnabled = (value) => {
|
|
|
134
134
|
};
|
|
135
135
|
//#endregion
|
|
136
136
|
//#region src/constants/rulesync-paths.ts
|
|
137
|
-
const { join: join$
|
|
137
|
+
const { join: join$290 } = node_path.posix;
|
|
138
138
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
139
139
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
140
140
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
141
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
142
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
143
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
144
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
145
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
146
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
147
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
148
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
149
|
-
join$
|
|
150
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
151
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
141
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
142
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$290(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
143
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
144
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
145
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
146
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
147
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
148
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
149
|
+
join$290(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
150
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
151
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
152
152
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
153
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
153
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
154
154
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
155
155
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
156
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
157
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
156
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$290(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
157
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$290(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
158
158
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
159
159
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
160
160
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -437,13 +437,15 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
437
437
|
"kiro-cli",
|
|
438
438
|
"kiro-ide",
|
|
439
439
|
"opencode",
|
|
440
|
+
"pi",
|
|
440
441
|
"qwencode",
|
|
441
442
|
"reasonix",
|
|
442
443
|
"rovodev",
|
|
443
444
|
"takt",
|
|
444
445
|
"vibe",
|
|
445
446
|
"warp",
|
|
446
|
-
"zed"
|
|
447
|
+
"zed",
|
|
448
|
+
"zoocode"
|
|
447
449
|
];
|
|
448
450
|
const checksProcessorToolTargetTuple = [
|
|
449
451
|
"amp",
|
|
@@ -1928,7 +1930,7 @@ function isPlainObject$1(value) {
|
|
|
1928
1930
|
/**
|
|
1929
1931
|
* Type guard to check if a value is an array of strings.
|
|
1930
1932
|
*/
|
|
1931
|
-
function isStringArray$
|
|
1933
|
+
function isStringArray$2(value) {
|
|
1932
1934
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1933
1935
|
}
|
|
1934
1936
|
//#endregion
|
|
@@ -2296,6 +2298,7 @@ const HOOK_EVENTS = [
|
|
|
2296
2298
|
"todoCreated",
|
|
2297
2299
|
"todoCompleted",
|
|
2298
2300
|
"stopFailure",
|
|
2301
|
+
"stopCancelled",
|
|
2299
2302
|
"instructionsLoaded",
|
|
2300
2303
|
"userPromptExpansion",
|
|
2301
2304
|
"postToolBatch",
|
|
@@ -2871,6 +2874,7 @@ const GROKCLI_HOOK_EVENTS = [
|
|
|
2871
2874
|
"permissionDenied",
|
|
2872
2875
|
"stop",
|
|
2873
2876
|
"stopFailure",
|
|
2877
|
+
"stopCancelled",
|
|
2874
2878
|
"notification",
|
|
2875
2879
|
"subagentStart",
|
|
2876
2880
|
"subagentStop",
|
|
@@ -2946,16 +2950,10 @@ const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CAN
|
|
|
2946
2950
|
/**
|
|
2947
2951
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
2948
2952
|
*
|
|
2949
|
-
* Hermes validates hook events against a fixed `VALID_HOOKS` set
|
|
2950
|
-
*
|
|
2951
|
-
*
|
|
2952
|
-
*
|
|
2953
|
-
* `on_session_reset`, `subagent_start`, `subagent_stop`, `pre_gateway_dispatch`,
|
|
2954
|
-
* `pre_approval_request`, `post_approval_response`, `transform_tool_result`,
|
|
2955
|
-
* `transform_terminal_output`, `transform_llm_output`, and the three
|
|
2956
|
-
* `kanban_task_*` events. Only the events with a clean 1:1 canonical equivalent
|
|
2957
|
-
* are mapped here. All other native events round-trip through
|
|
2958
|
-
* `hermesagent.hooks`.
|
|
2953
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
2954
|
+
* of v0.20.2 (`v2026.8.16`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
2955
|
+
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
2956
|
+
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
2959
2957
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
2960
2958
|
*/
|
|
2961
2959
|
const HERMESAGENT_HOOK_EVENTS = [
|
|
@@ -2968,6 +2966,21 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
2968
2966
|
"subagentStart",
|
|
2969
2967
|
"subagentStop"
|
|
2970
2968
|
];
|
|
2969
|
+
/**
|
|
2970
|
+
* Hermes Agent's native `VALID_HOOKS` set, in upstream source order.
|
|
2971
|
+
*
|
|
2972
|
+
* The authority is `hermes_cli/plugins.py`'s `VALID_HOOKS`, not the docs: at
|
|
2973
|
+
* v0.20.2 `website/docs/user-guide/features/plugins.md` says "26 lifecycle
|
|
2974
|
+
* events" and then lists 30, omitting the five `on_kanban_*` worker/task/dispatch
|
|
2975
|
+
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
2976
|
+
* Shell hooks are gated on the same set.
|
|
2977
|
+
*
|
|
2978
|
+
* Verified against the tag `v2026.8.16` (v0.20.2): `VALID_HOOKS` holds 37
|
|
2979
|
+
* entries, grown from 23 at `v2026.8.3` (v0.20.0). This list holds 36 of them —
|
|
2980
|
+
* see {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
2981
|
+
*
|
|
2982
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py
|
|
2983
|
+
*/
|
|
2971
2984
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
2972
2985
|
"pre_tool_call",
|
|
2973
2986
|
"post_tool_call",
|
|
@@ -2976,6 +2989,10 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2976
2989
|
"transform_llm_output",
|
|
2977
2990
|
"pre_llm_call",
|
|
2978
2991
|
"post_llm_call",
|
|
2992
|
+
"on_stream_start",
|
|
2993
|
+
"on_stream_delta",
|
|
2994
|
+
"on_stream_end",
|
|
2995
|
+
"on_interim_message",
|
|
2979
2996
|
"pre_verify",
|
|
2980
2997
|
"pre_api_request",
|
|
2981
2998
|
"post_api_request",
|
|
@@ -2984,16 +3001,39 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2984
3001
|
"on_session_end",
|
|
2985
3002
|
"on_session_finalize",
|
|
2986
3003
|
"on_session_reset",
|
|
3004
|
+
"on_skill_lifecycle",
|
|
2987
3005
|
"subagent_start",
|
|
2988
3006
|
"subagent_stop",
|
|
2989
3007
|
"pre_gateway_dispatch",
|
|
2990
3008
|
"pre_approval_request",
|
|
2991
3009
|
"post_approval_response",
|
|
3010
|
+
"pre_transcription",
|
|
2992
3011
|
"kanban_task_claimed",
|
|
2993
3012
|
"kanban_task_completed",
|
|
2994
|
-
"kanban_task_blocked"
|
|
3013
|
+
"kanban_task_blocked",
|
|
3014
|
+
"on_kanban_worker_spawned",
|
|
3015
|
+
"on_kanban_worker_exited",
|
|
3016
|
+
"on_kanban_worker_stale_claim",
|
|
3017
|
+
"on_kanban_task_updated",
|
|
3018
|
+
"on_kanban_dispatch_tick",
|
|
3019
|
+
"gateway_platform_event",
|
|
3020
|
+
"pre_command"
|
|
2995
3021
|
];
|
|
2996
3022
|
/**
|
|
3023
|
+
* `VALID_HOOKS` entries a SHELL hook may not register for. `VALID_HOOKS` doubles
|
|
3024
|
+
* as the shell-hook allow-list, but `SHELL_UNSUPPORTED_HOOKS` is subtracted from
|
|
3025
|
+
* it first: shell hooks cannot return these events' directives, so
|
|
3026
|
+
* `_parse_hooks_block` refuses the registration with a warning rather than
|
|
3027
|
+
* accepting it and dropping the return value.
|
|
3028
|
+
*
|
|
3029
|
+
* rulesync only ever writes shell hooks, so these are excluded from
|
|
3030
|
+
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
3031
|
+
*
|
|
3032
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
3033
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/agent/shell_hooks.py — `_parse_hooks_block`
|
|
3034
|
+
*/
|
|
3035
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
3036
|
+
/**
|
|
2997
3037
|
* Map canonical camelCase event names to Hermes Agent's native `VALID_HOOKS`
|
|
2998
3038
|
* snake_case keys under the `hooks:` block of `~/.hermes/config.yaml`.
|
|
2999
3039
|
*/
|
|
@@ -3610,6 +3650,7 @@ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
|
3610
3650
|
permissionDenied: "PermissionDenied",
|
|
3611
3651
|
stop: "Stop",
|
|
3612
3652
|
stopFailure: "StopFailure",
|
|
3653
|
+
stopCancelled: "StopCancelled",
|
|
3613
3654
|
notification: "Notification",
|
|
3614
3655
|
subagentStart: "SubagentStart",
|
|
3615
3656
|
subagentStop: "SubagentStop",
|
|
@@ -3884,6 +3925,25 @@ const McpServerSchema = zod_mini.z.looseObject({
|
|
|
3884
3925
|
kiroAutoApprove: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3885
3926
|
kiroAutoBlock: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3886
3927
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
3928
|
+
/**
|
|
3929
|
+
* The canonical per-server tool allowlist.
|
|
3930
|
+
*
|
|
3931
|
+
* **Collision rule for adapters.** Several tools spell an allowlist natively
|
|
3932
|
+
* under a name that also exists here, so a single server entry can carry both
|
|
3933
|
+
* a canonical field and its native counterpart. What the *native* key means
|
|
3934
|
+
* decides the resolution — not which one was written first:
|
|
3935
|
+
*
|
|
3936
|
+
* - Native key is a redundant spelling of the same concept, and both lists are
|
|
3937
|
+
* additive → **merge** them, so a config that already spells the field
|
|
3938
|
+
* natively keeps working (`kiroAutoApprove`/`kiroAutoBlock` in
|
|
3939
|
+
* `kiro-mcp.ts`).
|
|
3940
|
+
* - Native key is the same concept but rulesync owns the generated value, so
|
|
3941
|
+
* keeping both would be ambiguous → the **canonical value wins** and the
|
|
3942
|
+
* native one is dropped with a warning (`tools` in `copilotcli-mcp.ts`).
|
|
3943
|
+
* - Native key of the same name means something else entirely → **refuse the
|
|
3944
|
+
* canonical value** rather than write a shape the tool misreads (`tools` in
|
|
3945
|
+
* `codexcli-mcp.ts`, where Codex reads it as a per-tool approval table).
|
|
3946
|
+
*/
|
|
3887
3947
|
enabledTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3888
3948
|
disabledTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
3889
3949
|
});
|
|
@@ -4726,8 +4786,65 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4726
4786
|
dangerouslyAllowAll: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4727
4787
|
});
|
|
4728
4788
|
/**
|
|
4789
|
+
* Tool-scoped override block for Pi Coding Agent. Pi has no allow/ask/deny rule
|
|
4790
|
+
* surface: the only repository-syncable tool gate is `defaultTools`, the list of
|
|
4791
|
+
* built-in tools enabled at startup (added in v0.84.2). It is an enable-list
|
|
4792
|
+
* rather than a canonical allow/deny rule set, so it is authored here rather
|
|
4793
|
+
* than mapped onto `permission`, mirroring `KimiCodePermissionsOverrideSchema`'s
|
|
4794
|
+
* `tools.enabled` and `VibePermissionsOverrideSchema`'s `enabled_tools`.
|
|
4795
|
+
*
|
|
4796
|
+
* An empty array is meaningful upstream — it starts Pi with no built-in tools
|
|
4797
|
+
* while keeping extension and SDK custom tools — so it is emitted as written.
|
|
4798
|
+
* CLI flags (`--tools`, `--no-tools`, `--no-builtin-tools`, `--exclude-tools`)
|
|
4799
|
+
* outrank the setting, and a project array **replaces** the global one rather
|
|
4800
|
+
* than merging with it.
|
|
4801
|
+
*
|
|
4802
|
+
* @example
|
|
4803
|
+
* { "defaultTools": ["bash", "edit", "write"] }
|
|
4804
|
+
* @see https://pi.dev/docs/latest/settings
|
|
4805
|
+
*/
|
|
4806
|
+
const PiPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4807
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4808
|
+
defaultTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4809
|
+
});
|
|
4810
|
+
/**
|
|
4811
|
+
* The Antigravity CLI's four documented `toolPermission` autonomy presets.
|
|
4812
|
+
* Exported so the import side of `antigravity-cli-permissions.ts` filters
|
|
4813
|
+
* against exactly the values the enum below accepts — an unrecognized value
|
|
4814
|
+
* carried in verbatim would fail validation of the *whole*
|
|
4815
|
+
* `.rulesync/permissions.jsonc`, not just that key.
|
|
4816
|
+
* @see https://antigravity.google/docs/cli/reference
|
|
4817
|
+
*/
|
|
4818
|
+
const ANTIGRAVITY_CLI_TOOL_PERMISSIONS = [
|
|
4819
|
+
"request-review",
|
|
4820
|
+
"proceed-in-sandbox",
|
|
4821
|
+
"always-proceed",
|
|
4822
|
+
"strict"
|
|
4823
|
+
];
|
|
4824
|
+
/**
|
|
4825
|
+
* The Antigravity CLI's three documented `artifactReviewPolicy` values. Shared
|
|
4826
|
+
* with the import filter for the same reason as
|
|
4827
|
+
* {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4828
|
+
* @see https://antigravity.google/docs/cli/settings
|
|
4829
|
+
*/
|
|
4830
|
+
const ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES = [
|
|
4831
|
+
"asks-for-review",
|
|
4832
|
+
"agent-decides",
|
|
4833
|
+
"always-proceed"
|
|
4834
|
+
];
|
|
4835
|
+
/**
|
|
4836
|
+
* The baseline execution modes the CLI persists as `agentMode`. Shared with the
|
|
4837
|
+
* import filter for the same reason as {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4838
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4839
|
+
*/
|
|
4840
|
+
const ANTIGRAVITY_CLI_AGENT_MODES = [
|
|
4841
|
+
"default",
|
|
4842
|
+
"accept-edits",
|
|
4843
|
+
"plan"
|
|
4844
|
+
];
|
|
4845
|
+
/**
|
|
4729
4846
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4730
|
-
* `settings.json` carries
|
|
4847
|
+
* `settings.json` carries five global autonomy/sandbox knobs outside the
|
|
4731
4848
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4732
4849
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4733
4850
|
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
@@ -4735,7 +4852,8 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4735
4852
|
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4736
4853
|
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4737
4854
|
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4738
|
-
* active workspace roots)
|
|
4855
|
+
* active workspace roots), and `agentMode` (the baseline execution mode a session
|
|
4856
|
+
* starts in — `default` / `accept-edits` / `plan`). Antigravity applies the allow/deny
|
|
4739
4857
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4740
4858
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4741
4859
|
* Fields placed here are merged onto the top level of
|
|
@@ -4743,27 +4861,27 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4743
4861
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4744
4862
|
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
4745
4863
|
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4746
|
-
* https://antigravity.google/docs/cli/sandbox
|
|
4747
|
-
* https://antigravity.google/docs/cli/settings
|
|
4864
|
+
* https://antigravity.google/docs/cli/sandbox,
|
|
4865
|
+
* https://antigravity.google/docs/cli/settings and
|
|
4866
|
+
* https://antigravity.google/docs/cli/modes.
|
|
4748
4867
|
*
|
|
4749
4868
|
* @example
|
|
4750
4869
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
4751
4870
|
*/
|
|
4752
4871
|
const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4753
4872
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4754
|
-
toolPermission: zod_mini.z.optional(zod_mini.z.enum(
|
|
4755
|
-
"request-review",
|
|
4756
|
-
"proceed-in-sandbox",
|
|
4757
|
-
"always-proceed",
|
|
4758
|
-
"strict"
|
|
4759
|
-
])),
|
|
4873
|
+
toolPermission: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_TOOL_PERMISSIONS)),
|
|
4760
4874
|
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4761
|
-
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum(
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4875
|
+
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES)),
|
|
4876
|
+
allowNonWorkspaceAccess: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4877
|
+
/**
|
|
4878
|
+
* The baseline execution mode the CLI starts a session in (Agent Mode), the
|
|
4879
|
+
* persisted form of the `/settings` panel's Agent Mode row. Only
|
|
4880
|
+
* `~/.gemini/antigravity-cli/settings.json` is read for it, which is why this
|
|
4881
|
+
* override stays global-only.
|
|
4882
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4883
|
+
*/
|
|
4884
|
+
agentMode: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_AGENT_MODES))
|
|
4767
4885
|
});
|
|
4768
4886
|
/**
|
|
4769
4887
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -5073,6 +5191,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
5073
5191
|
takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
|
|
5074
5192
|
amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
|
|
5075
5193
|
"antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema),
|
|
5194
|
+
pi: zod_mini.z.optional(PiPermissionsOverrideSchema),
|
|
5076
5195
|
augmentcode: zod_mini.z.optional(AugmentcodePermissionsOverrideSchema),
|
|
5077
5196
|
kiro: zod_mini.z.optional(KiroPermissionsOverrideSchema),
|
|
5078
5197
|
codexcli: zod_mini.z.optional(CodexcliPermissionsOverrideSchema),
|
|
@@ -5252,6 +5371,7 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
5252
5371
|
systemPrompt: zod_mini.z.optional(zod_mini.z.enum(["append"])),
|
|
5253
5372
|
contextFile: zod_mini.z.optional(zod_mini.z.enum(["override"]))
|
|
5254
5373
|
})),
|
|
5374
|
+
roo: zod_mini.z.optional(zod_mini.z.looseObject({ mode: zod_mini.z.optional(zod_mini.z.string()) })),
|
|
5255
5375
|
takt: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5256
5376
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
5257
5377
|
extends: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -5554,6 +5674,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
5554
5674
|
cursor: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5555
5675
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
5556
5676
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5677
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5557
5678
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
5558
5679
|
})),
|
|
5559
5680
|
factorydroid: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -5778,6 +5899,11 @@ async function getLocalSkillDirNames(outputRoot) {
|
|
|
5778
5899
|
* default with a per-target value. A defined section value (including `false`)
|
|
5779
5900
|
* always wins over the root default.
|
|
5780
5901
|
*
|
|
5902
|
+
* `devin` also consumes the root-level value — it maps `true` onto a user-only
|
|
5903
|
+
* `triggers` list (see `devin-skill.ts`) — but has no section key of the same
|
|
5904
|
+
* name, so it does not go through this helper. A `devin.triggers` section value
|
|
5905
|
+
* still overrides it.
|
|
5906
|
+
*
|
|
5781
5907
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5782
5908
|
*/
|
|
5783
5909
|
function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
@@ -5788,10 +5914,15 @@ function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
|
5788
5914
|
*
|
|
5789
5915
|
* The rulesync skill frontmatter exposes a root-level `user-invocable` default
|
|
5790
5916
|
* that applies to every tool supporting the flag (claudecode, copilot,
|
|
5791
|
-
* copilotcli, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
|
|
5917
|
+
* copilotcli, cursor, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
|
|
5792
5918
|
* per-target value. A defined section value (including `false`) always wins
|
|
5793
5919
|
* over the root default.
|
|
5794
5920
|
*
|
|
5921
|
+
* `devin` also consumes the root-level value — it maps `false` onto a
|
|
5922
|
+
* model-only `triggers` list (see `devin-skill.ts`) — but has no section key of
|
|
5923
|
+
* the same name, so it does not go through this helper. A `devin.triggers`
|
|
5924
|
+
* section value still overrides it.
|
|
5925
|
+
*
|
|
5795
5926
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5796
5927
|
*/
|
|
5797
5928
|
function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
@@ -7641,6 +7772,22 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7641
7772
|
}
|
|
7642
7773
|
}
|
|
7643
7774
|
},
|
|
7775
|
+
".pi/settings.json": {
|
|
7776
|
+
format: "json",
|
|
7777
|
+
invalidRootPolicy: "error",
|
|
7778
|
+
features: { permissions: {
|
|
7779
|
+
kind: "replace-owned-keys",
|
|
7780
|
+
ownedKeys: ["defaultTools"]
|
|
7781
|
+
} }
|
|
7782
|
+
},
|
|
7783
|
+
".pi/agent/settings.json": {
|
|
7784
|
+
format: "json",
|
|
7785
|
+
invalidRootPolicy: "error",
|
|
7786
|
+
features: { permissions: {
|
|
7787
|
+
kind: "replace-owned-keys",
|
|
7788
|
+
ownedKeys: ["defaultTools"]
|
|
7789
|
+
} }
|
|
7790
|
+
},
|
|
7644
7791
|
".github/copilot/settings.json": {
|
|
7645
7792
|
format: "json",
|
|
7646
7793
|
invalidRootPolicy: "error",
|
|
@@ -7666,7 +7813,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7666
7813
|
ownedKeys: [
|
|
7667
7814
|
"chat.tools.terminal.autoApprove",
|
|
7668
7815
|
"chat.tools.edits.autoApprove",
|
|
7669
|
-
"chat.tools.urls.autoApprove"
|
|
7816
|
+
"chat.tools.urls.autoApprove",
|
|
7817
|
+
"zoo-code.allowedCommands",
|
|
7818
|
+
"zoo-code.deniedCommands"
|
|
7670
7819
|
]
|
|
7671
7820
|
} }
|
|
7672
7821
|
},
|
|
@@ -12227,6 +12376,8 @@ const PI_RULE_FILE_NAME = "AGENTS.md";
|
|
|
12227
12376
|
const PI_RULE_OVERRIDE_FILE_NAME = "AGENTS.override.md";
|
|
12228
12377
|
const PI_APPEND_SYSTEM_FILE_NAME = "APPEND_SYSTEM.md";
|
|
12229
12378
|
const PI_HOOKS_FILE_NAME = "rulesync-hooks.ts";
|
|
12379
|
+
const PI_SETTINGS_FILE_NAME = "settings.json";
|
|
12380
|
+
const PI_AGENT_DIR_PATH = PI_AGENT_DIR;
|
|
12230
12381
|
//#endregion
|
|
12231
12382
|
//#region src/features/commands/pi-command.ts
|
|
12232
12383
|
/**
|
|
@@ -12628,6 +12779,21 @@ const ROO_DIR = ".roo";
|
|
|
12628
12779
|
const ROO_COMMANDS_DIR_PATH = (0, node_path.join)(ROO_DIR, "commands");
|
|
12629
12780
|
const ROO_SKILLS_DIR_PATH = (0, node_path.join)(ROO_DIR, "skills");
|
|
12630
12781
|
const ROO_MCP_FILE_NAME = "mcp.json";
|
|
12782
|
+
/**
|
|
12783
|
+
* Mode slugs Roo/Zoo Code themselves accept for a `rules-{mode}` directory:
|
|
12784
|
+
* the loader builds the directory name by interpolating the active mode slug,
|
|
12785
|
+
* and custom-mode slugs are restricted to this alphabet. Validating against it
|
|
12786
|
+
* also keeps an authored value from escaping `.roo/` through path separators or
|
|
12787
|
+
* `..` segments.
|
|
12788
|
+
*/
|
|
12789
|
+
const ROO_MODE_SLUG_PATTERN = /^[a-zA-Z0-9-]+$/;
|
|
12790
|
+
/**
|
|
12791
|
+
* `.roo/rules-{mode}/` — the mode-specific rule directory Roo/Zoo Code load
|
|
12792
|
+
* INSTEAD of `.roo/rules/` while that mode is active. The relative path is the
|
|
12793
|
+
* same in global scope, where it resolves under `~/.roo/`.
|
|
12794
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/core/prompts/sections/custom-instructions.ts
|
|
12795
|
+
*/
|
|
12796
|
+
const rooModeRulesDirName = (mode) => `rules-${mode}`;
|
|
12631
12797
|
const ROO_IGNORE_FILE_NAME = ".rooignore";
|
|
12632
12798
|
/**
|
|
12633
12799
|
* Roo Code reads project-level custom modes from a single aggregated
|
|
@@ -14151,7 +14317,7 @@ const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
|
14151
14317
|
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
14152
14318
|
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
14153
14319
|
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
14154
|
-
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
14320
|
+
const isEmittableArray = ({ value }) => isStringArray$1(value);
|
|
14155
14321
|
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
14156
14322
|
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
14157
14323
|
/**
|
|
@@ -14460,7 +14626,7 @@ function isStringRecord(value) {
|
|
|
14460
14626
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
14461
14627
|
return Object.values(value).every((v) => typeof v === "string");
|
|
14462
14628
|
}
|
|
14463
|
-
function isStringArray(value) {
|
|
14629
|
+
function isStringArray$1(value) {
|
|
14464
14630
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
14465
14631
|
}
|
|
14466
14632
|
/** Compare object values without letting key order decide the answer. */
|
|
@@ -14492,7 +14658,7 @@ function isSafeStringRecord(value) {
|
|
|
14492
14658
|
* skipped wholesale when that read fails.
|
|
14493
14659
|
*/
|
|
14494
14660
|
function isSafeStringArray(value) {
|
|
14495
|
-
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14661
|
+
return isStringArray$1(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14496
14662
|
}
|
|
14497
14663
|
/**
|
|
14498
14664
|
* A raw string kept only if the canonical field it would land in accepts it,
|
|
@@ -14534,7 +14700,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
14534
14700
|
return {
|
|
14535
14701
|
...url !== void 0 && { url },
|
|
14536
14702
|
...isStringRecord(headers) && { headers },
|
|
14537
|
-
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14703
|
+
...isStringArray$1(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14538
14704
|
};
|
|
14539
14705
|
}
|
|
14540
14706
|
if (hookType === "mcp_tool") {
|
|
@@ -15517,7 +15683,7 @@ const MANAGED_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_CLINE_EVENT_NAMES
|
|
|
15517
15683
|
function parseManifest(fileContent) {
|
|
15518
15684
|
try {
|
|
15519
15685
|
const parsed = JSON.parse(fileContent);
|
|
15520
|
-
if (!isRecord$1(parsed) || !isStringArray$
|
|
15686
|
+
if (!isRecord$1(parsed) || !isStringArray$2(parsed.events)) return null;
|
|
15521
15687
|
return {
|
|
15522
15688
|
generatedBy: "rulesync",
|
|
15523
15689
|
events: parsed.events.filter((event) => MANAGED_EVENT_NAMES.has(event))
|
|
@@ -17152,6 +17318,7 @@ const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["pre_tool_call", "po
|
|
|
17152
17318
|
const HERMESAGENT_FAIL_CLOSED_EVENT = "pre_tool_call";
|
|
17153
17319
|
const HERMESAGENT_CANONICAL_EVENTS = new Set(HERMESAGENT_HOOK_EVENTS);
|
|
17154
17320
|
const HERMESAGENT_NATIVE_EVENTS = new Set(HERMESAGENT_NATIVE_HOOK_EVENTS);
|
|
17321
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_EVENTS = new Set(HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS);
|
|
17155
17322
|
/**
|
|
17156
17323
|
* Whether an entry of the `hooks:` mapping is a hook-event list rather than one
|
|
17157
17324
|
* of its non-event siblings.
|
|
@@ -17240,7 +17407,8 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
17240
17407
|
}
|
|
17241
17408
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
17242
17409
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
17243
|
-
if (
|
|
17410
|
+
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.`);
|
|
17411
|
+
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.`);
|
|
17244
17412
|
setHermesHookEntries({
|
|
17245
17413
|
result,
|
|
17246
17414
|
event: nativeEvent,
|
|
@@ -22364,7 +22532,7 @@ function isCodexStdioServer(config) {
|
|
|
22364
22532
|
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
22365
22533
|
*/
|
|
22366
22534
|
function isValidRenamedArray(key, value) {
|
|
22367
|
-
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$
|
|
22535
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$2(value);
|
|
22368
22536
|
}
|
|
22369
22537
|
/**
|
|
22370
22538
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
@@ -22884,6 +23052,49 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
22884
23052
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
22885
23053
|
};
|
|
22886
23054
|
/**
|
|
23055
|
+
* Copilot CLI's per-server tool allowlist. `tools` selects which of the
|
|
23056
|
+
* server's tools are exposed — `["*"]` (the default) for all, or a list of
|
|
23057
|
+
* names — so the canonical `enabledTools` is written under that name rather
|
|
23058
|
+
* than passed through as a key the CLI ignores.
|
|
23059
|
+
*
|
|
23060
|
+
* When a server carries both, the canonical `enabledTools` wins and the native
|
|
23061
|
+
* `tools` is dropped with a warning. rulesync owns the generated allowlist, and
|
|
23062
|
+
* the `tools` value most likely present is the upstream default `["*"]` — left
|
|
23063
|
+
* to win, it would silently discard a real allowlist in favour of "expose every
|
|
23064
|
+
* tool", which is the failure this mapping exists to prevent. That is the
|
|
23065
|
+
* "canonical wins" branch of the collision rule documented on `enabledTools` in
|
|
23066
|
+
* `src/types/mcp.ts`; `kiro-mcp.ts` merges instead (its native names are
|
|
23067
|
+
* redundant spellings of additive lists) and `codexcli-mcp.ts` refuses instead
|
|
23068
|
+
* (Codex's same-named key means a different thing).
|
|
23069
|
+
*
|
|
23070
|
+
* `disabledTools` has no counterpart upstream (expressing it would need the
|
|
23071
|
+
* server's full tool list), so the target keeps `supportsDisabledTools: false`
|
|
23072
|
+
* and the processor strips it before it reaches here.
|
|
23073
|
+
*
|
|
23074
|
+
* @see https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
|
|
23075
|
+
*/
|
|
23076
|
+
function renameEnabledToolsToTools({ server, serverName, logger }) {
|
|
23077
|
+
const { enabledTools, ...rest } = server;
|
|
23078
|
+
if (enabledTools === void 0) return rest;
|
|
23079
|
+
if (rest.tools !== void 0) logger?.warn(`[CopilotcliMcp] MCP server "${serverName}" declares both 'tools' and 'enabledTools'; keeping 'enabledTools' and dropping 'tools'.`);
|
|
23080
|
+
return {
|
|
23081
|
+
...rest,
|
|
23082
|
+
tools: enabledTools
|
|
23083
|
+
};
|
|
23084
|
+
}
|
|
23085
|
+
/**
|
|
23086
|
+
* Copilot CLI's `tools` accepts more than the canonical `enabledTools` shape:
|
|
23087
|
+
* `--tools` documents `*`, a comma-separated list, or `""`, so a hand-written or
|
|
23088
|
+
* CLI-written entry can be a bare string. Canonical `enabledTools` is
|
|
23089
|
+
* `string[]`, and `RulesyncMcp` throws on a schema failure — carrying a string
|
|
23090
|
+
* through would reject the WHOLE imported MCP file rather than one key. Anything
|
|
23091
|
+
* that is not a string array is therefore left under `tools` untouched, matching
|
|
23092
|
+
* `codexcli-mcp.ts`'s `isValidRenamedArray` guard on the same class of rename.
|
|
23093
|
+
*/
|
|
23094
|
+
function isStringArray(value) {
|
|
23095
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
23096
|
+
}
|
|
23097
|
+
/**
|
|
22887
23098
|
* Resolves and sets the transport type for each MCP server config.
|
|
22888
23099
|
* GitHub Copilot CLI requires the "type" field for each server. A server it
|
|
22889
23100
|
* cannot express — one that names no transport, or names one it carries no way
|
|
@@ -22927,7 +23138,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22927
23138
|
}
|
|
22928
23139
|
const { httpUrl: _httpUrl, ...rest } = parsed;
|
|
22929
23140
|
result[name] = {
|
|
22930
|
-
...
|
|
23141
|
+
...renameEnabledToolsToTools({
|
|
23142
|
+
server: rest,
|
|
23143
|
+
serverName: name,
|
|
23144
|
+
logger
|
|
23145
|
+
}),
|
|
22931
23146
|
type,
|
|
22932
23147
|
url
|
|
22933
23148
|
};
|
|
@@ -22945,7 +23160,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22945
23160
|
continue;
|
|
22946
23161
|
}
|
|
22947
23162
|
result[name] = {
|
|
22948
|
-
...
|
|
23163
|
+
...renameEnabledToolsToTools({
|
|
23164
|
+
server: local,
|
|
23165
|
+
serverName: name,
|
|
23166
|
+
logger
|
|
23167
|
+
}),
|
|
22949
23168
|
type,
|
|
22950
23169
|
command: head,
|
|
22951
23170
|
...tail.length > 0 && { args: tail }
|
|
@@ -22959,11 +23178,19 @@ function addTypeField(mcpServers, logger) {
|
|
|
22959
23178
|
function removeTypeField(config) {
|
|
22960
23179
|
const result = {};
|
|
22961
23180
|
for (const [name, server] of Object.entries(config.mcpServers ?? {})) {
|
|
22962
|
-
|
|
22963
|
-
|
|
23181
|
+
const { tools, ...withoutTools } = server;
|
|
23182
|
+
const restored = isStringArray(tools) ? {
|
|
23183
|
+
...withoutTools,
|
|
23184
|
+
enabledTools: tools
|
|
23185
|
+
} : {
|
|
23186
|
+
...withoutTools,
|
|
23187
|
+
...tools !== void 0 && { tools }
|
|
23188
|
+
};
|
|
23189
|
+
if (restored.type !== "stdio") {
|
|
23190
|
+
result[name] = restored;
|
|
22964
23191
|
continue;
|
|
22965
23192
|
}
|
|
22966
|
-
const { type: _, ...rest } =
|
|
23193
|
+
const { type: _, ...rest } = restored;
|
|
22967
23194
|
result[name] = rest;
|
|
22968
23195
|
}
|
|
22969
23196
|
return result;
|
|
@@ -23615,11 +23842,11 @@ function applyGooseStdioFields(ext, config) {
|
|
|
23615
23842
|
if (Array.isArray(command)) {
|
|
23616
23843
|
if (typeof command[0] === "string") ext.cmd = command[0];
|
|
23617
23844
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23618
|
-
const args = isStringArray$
|
|
23845
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23619
23846
|
if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
|
|
23620
23847
|
} else if (typeof command === "string") {
|
|
23621
23848
|
ext.cmd = command;
|
|
23622
|
-
if (isStringArray$
|
|
23849
|
+
if (isStringArray$2(config.args)) ext.args = config.args;
|
|
23623
23850
|
}
|
|
23624
23851
|
if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
|
|
23625
23852
|
}
|
|
@@ -23721,7 +23948,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
23721
23948
|
else if (type === "streamable_http") server.type = "http";
|
|
23722
23949
|
else server.type = "stdio";
|
|
23723
23950
|
if (typeof ext.cmd === "string") server.command = ext.cmd;
|
|
23724
|
-
if (isStringArray$
|
|
23951
|
+
if (isStringArray$2(ext.args)) server.args = ext.args;
|
|
23725
23952
|
if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
|
|
23726
23953
|
if (typeof ext.uri === "string") server.url = ext.uri;
|
|
23727
23954
|
if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
|
|
@@ -23742,11 +23969,11 @@ function buildGoosePluginStdioServer(config) {
|
|
|
23742
23969
|
if (Array.isArray(command)) {
|
|
23743
23970
|
if (typeof command[0] === "string") server.command = command[0];
|
|
23744
23971
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23745
|
-
const args = isStringArray$
|
|
23972
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23746
23973
|
if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
|
|
23747
23974
|
} else if (typeof command === "string") {
|
|
23748
23975
|
server.command = command;
|
|
23749
|
-
if (isStringArray$
|
|
23976
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
23750
23977
|
}
|
|
23751
23978
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
23752
23979
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -24078,7 +24305,7 @@ function copyHermesOauth(source) {
|
|
|
24078
24305
|
"client_secret"
|
|
24079
24306
|
]) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
24080
24307
|
if (typeof source.redirect_port === "number") oauth.redirect_port = source.redirect_port;
|
|
24081
|
-
if (isStringArray$
|
|
24308
|
+
if (isStringArray$2(source.scopes)) oauth.scopes = source.scopes;
|
|
24082
24309
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
24083
24310
|
}
|
|
24084
24311
|
function copyHermesAdvancedFields(source, target) {
|
|
@@ -24087,7 +24314,7 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
24087
24314
|
target.auth = source.auth;
|
|
24088
24315
|
copied = true;
|
|
24089
24316
|
}
|
|
24090
|
-
if (typeof source.client_cert === "string" || isStringArray$
|
|
24317
|
+
if (typeof source.client_cert === "string" || isStringArray$2(source.client_cert)) {
|
|
24091
24318
|
target.client_cert = source.client_cert;
|
|
24092
24319
|
copied = true;
|
|
24093
24320
|
}
|
|
@@ -24156,8 +24383,8 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
24156
24383
|
*/
|
|
24157
24384
|
function buildHermesToolsBlock(config) {
|
|
24158
24385
|
const tools = {};
|
|
24159
|
-
if (isStringArray$
|
|
24160
|
-
if (isStringArray$
|
|
24386
|
+
if (isStringArray$2(config.enabledTools)) tools.include = config.enabledTools;
|
|
24387
|
+
if (isStringArray$2(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
24161
24388
|
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
24162
24389
|
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
24163
24390
|
return tools;
|
|
@@ -24169,8 +24396,8 @@ function buildHermesToolsBlock(config) {
|
|
|
24169
24396
|
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
24170
24397
|
*/
|
|
24171
24398
|
function applyHermesToolsBlock(hermesTools, server) {
|
|
24172
|
-
if (isStringArray$
|
|
24173
|
-
if (isStringArray$
|
|
24399
|
+
if (isStringArray$2(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
24400
|
+
if (isStringArray$2(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
24174
24401
|
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
24175
24402
|
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
24176
24403
|
}
|
|
@@ -24196,11 +24423,11 @@ function convertServerToHermes(config) {
|
|
|
24196
24423
|
if (Array.isArray(command)) {
|
|
24197
24424
|
if (typeof command[0] === "string") out.command = command[0];
|
|
24198
24425
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
24199
|
-
const args = isStringArray$
|
|
24426
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
24200
24427
|
if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
|
|
24201
24428
|
} else if (typeof command === "string") {
|
|
24202
24429
|
out.command = command;
|
|
24203
|
-
if (isStringArray$
|
|
24430
|
+
if (isStringArray$2(config.args)) out.args = config.args;
|
|
24204
24431
|
}
|
|
24205
24432
|
if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
|
|
24206
24433
|
} else if (url !== void 0) {
|
|
@@ -24250,7 +24477,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
24250
24477
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
24251
24478
|
const server = {};
|
|
24252
24479
|
if (typeof config.command === "string") server.command = config.command;
|
|
24253
|
-
if (isStringArray$
|
|
24480
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
24254
24481
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
24255
24482
|
if (typeof config.url === "string") server.url = config.url;
|
|
24256
24483
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
@@ -25208,13 +25435,17 @@ function mergeToolLists(...lists) {
|
|
|
25208
25435
|
*
|
|
25209
25436
|
* Both native names are documented per-server fields, so a config that already
|
|
25210
25437
|
* spells them natively keeps working: the two lists are merged rather than
|
|
25211
|
-
* one overwriting the other.
|
|
25438
|
+
* one overwriting the other. That is the "merge" branch of the collision rule
|
|
25439
|
+
* documented on `enabledTools` in `src/types/mcp.ts` — it applies here because
|
|
25440
|
+
* the native names are redundant spellings of the same additive lists;
|
|
25441
|
+
* `copilotcli-mcp.ts` lets the canonical value win instead, and
|
|
25442
|
+
* `codexcli-mcp.ts` refuses the canonical value instead.
|
|
25212
25443
|
* @see https://kiro.dev/docs/mcp/configuration/
|
|
25213
25444
|
*/
|
|
25214
25445
|
function toKiroMcpServers(servers) {
|
|
25215
25446
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25216
25447
|
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
25217
|
-
const autoApprove = mergeToolLists(isStringArray$
|
|
25448
|
+
const autoApprove = mergeToolLists(isStringArray$2(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
25218
25449
|
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
25219
25450
|
return [name, {
|
|
25220
25451
|
...rest,
|
|
@@ -25238,7 +25469,7 @@ function fromKiroMcpServers(servers) {
|
|
|
25238
25469
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25239
25470
|
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
25240
25471
|
const { autoApprove, ...rest } = server;
|
|
25241
|
-
if (!isStringArray$
|
|
25472
|
+
if (!isStringArray$2(autoApprove)) return [name, server];
|
|
25242
25473
|
return [name, {
|
|
25243
25474
|
...rest,
|
|
25244
25475
|
kiroAutoApprove: autoApprove
|
|
@@ -26380,7 +26611,7 @@ async function readRovodevConfigYaml({ outputRoot }) {
|
|
|
26380
26611
|
}
|
|
26381
26612
|
function disabledNamesOf(config) {
|
|
26382
26613
|
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
26383
|
-
return isStringArray$
|
|
26614
|
+
return isStringArray$2(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
26384
26615
|
}
|
|
26385
26616
|
/**
|
|
26386
26617
|
* Auxiliary writer for the `mcp:` block of `.rovodev/config.yml` (project) /
|
|
@@ -26513,7 +26744,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
26513
26744
|
return [];
|
|
26514
26745
|
}
|
|
26515
26746
|
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
26516
|
-
const existingDisabled = isStringArray$
|
|
26747
|
+
const existingDisabled = isStringArray$2(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
26517
26748
|
const managedNameSet = new Set(managedNames);
|
|
26518
26749
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
26519
26750
|
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.`);
|
|
@@ -27355,7 +27586,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27355
27586
|
meta: {
|
|
27356
27587
|
supportsProject: true,
|
|
27357
27588
|
supportsGlobal: true,
|
|
27358
|
-
supportsEnabledTools:
|
|
27589
|
+
supportsEnabledTools: true,
|
|
27359
27590
|
supportsDisabledTools: false
|
|
27360
27591
|
}
|
|
27361
27592
|
}],
|
|
@@ -27588,6 +27819,27 @@ const defaultGetFactory$3 = (target) => {
|
|
|
27588
27819
|
if (!factory) throw new Error(`Unsupported tool target: ${target}`);
|
|
27589
27820
|
return factory;
|
|
27590
27821
|
};
|
|
27822
|
+
/**
|
|
27823
|
+
* Warn about per-server tool filters the target tool cannot express.
|
|
27824
|
+
*
|
|
27825
|
+
* `enabledTools`/`disabledTools` are canonical fields, so a single
|
|
27826
|
+
* `.rulesync/.mcp.json` can carry a filter that only some targets read. The
|
|
27827
|
+
* unsupported ones are stripped before generation (writing them would produce a
|
|
27828
|
+
* key the tool discards on load), which used to happen silently — the filter
|
|
27829
|
+
* simply did not apply and nothing said so. Warning names the servers whose
|
|
27830
|
+
* filter is being dropped for this target so the gap is visible at generate
|
|
27831
|
+
* time rather than in the tool's behavior.
|
|
27832
|
+
*
|
|
27833
|
+
* Only fields actually present are reported, so a config that never authored
|
|
27834
|
+
* the filter stays quiet.
|
|
27835
|
+
*/
|
|
27836
|
+
function warnStrippedMcpServerFields({ mcpServers, fields, toolTarget, logger }) {
|
|
27837
|
+
for (const field of fields) {
|
|
27838
|
+
const serverNames = Object.entries(mcpServers ?? {}).filter(([, serverConfig]) => serverConfig[field] !== void 0).map(([serverName]) => serverName);
|
|
27839
|
+
if (serverNames.length === 0) continue;
|
|
27840
|
+
logger.warn(`${toolTarget} does not read the per-server \`${field}\` MCP tool filter; dropping it from ${serverNames.join(", ")}.`);
|
|
27841
|
+
}
|
|
27842
|
+
}
|
|
27591
27843
|
var McpProcessor = class extends FeatureProcessor {
|
|
27592
27844
|
toolTarget;
|
|
27593
27845
|
global;
|
|
@@ -27667,6 +27919,12 @@ var McpProcessor = class extends FeatureProcessor {
|
|
|
27667
27919
|
const fieldsToStrip = [];
|
|
27668
27920
|
if (!factory.meta.supportsEnabledTools) fieldsToStrip.push("enabledTools");
|
|
27669
27921
|
if (!factory.meta.supportsDisabledTools) fieldsToStrip.push("disabledTools");
|
|
27922
|
+
warnStrippedMcpServerFields({
|
|
27923
|
+
mcpServers: targetedRulesyncMcp.getJson().mcpServers,
|
|
27924
|
+
fields: fieldsToStrip,
|
|
27925
|
+
toolTarget: this.toolTarget,
|
|
27926
|
+
logger: this.logger
|
|
27927
|
+
});
|
|
27670
27928
|
const filteredRulesyncMcp = targetedRulesyncMcp.stripMcpServerFields(fieldsToStrip);
|
|
27671
27929
|
return await factory.class.fromRulesyncMcp({
|
|
27672
27930
|
outputRoot: this.outputRoot,
|
|
@@ -28179,6 +28437,34 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
28179
28437
|
//#endregion
|
|
28180
28438
|
//#region src/features/permissions/antigravity-cli-permissions.ts
|
|
28181
28439
|
/**
|
|
28440
|
+
* Top-level `~/.gemini/antigravity-cli/settings.json` keys the `antigravity-cli`
|
|
28441
|
+
* permissions override may author. They are siblings of `permissions`, passed
|
|
28442
|
+
* through verbatim in both directions.
|
|
28443
|
+
*/
|
|
28444
|
+
const ANTIGRAVITY_CLI_OVERRIDE_KEYS = [
|
|
28445
|
+
"toolPermission",
|
|
28446
|
+
"enableTerminalSandbox",
|
|
28447
|
+
"artifactReviewPolicy",
|
|
28448
|
+
"allowNonWorkspaceAccess",
|
|
28449
|
+
"agentMode"
|
|
28450
|
+
];
|
|
28451
|
+
const moduleLogger$2 = fallbackLogger;
|
|
28452
|
+
/**
|
|
28453
|
+
* Narrow an imported `settings.json` value to one of the documented values of
|
|
28454
|
+
* an enum-typed override key. The override schema declares `toolPermission`,
|
|
28455
|
+
* `artifactReviewPolicy` and `agentMode` as `z.enum`, so carrying an
|
|
28456
|
+
* unrecognized value through — a hand-written typo, or a preset a newer
|
|
28457
|
+
* Antigravity release added — would fail validation of the whole imported
|
|
28458
|
+
* `.rulesync/permissions.jsonc` instead of dropping just that key. A dropped
|
|
28459
|
+
* value is warned about rather than discarded silently, so a preset rulesync
|
|
28460
|
+
* does not know about yet is visible instead of quietly missing.
|
|
28461
|
+
*/
|
|
28462
|
+
function pickDocumentedValue({ key, value, documented }) {
|
|
28463
|
+
if (typeof value !== "string") return void 0;
|
|
28464
|
+
if (documented.includes(value)) return value;
|
|
28465
|
+
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.`);
|
|
28466
|
+
}
|
|
28467
|
+
/**
|
|
28182
28468
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
28183
28469
|
* Antigravity CLI Fine-Grained Permissions Engine action vocabulary
|
|
28184
28470
|
* (`read_file`, `write_file`, `read_url`, `command`, `mcp`, plus `execute_url` /
|
|
@@ -28253,9 +28539,10 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
28253
28539
|
* file (global scope only). The file holds other CLI settings besides
|
|
28254
28540
|
* permissions, so it is never deleted.
|
|
28255
28541
|
*
|
|
28256
|
-
*
|
|
28542
|
+
* Five CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
28257
28543
|
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
28258
|
-
* `artifactReviewPolicy` and `
|
|
28544
|
+
* `artifactReviewPolicy`, `allowNonWorkspaceAccess` and `agentMode` (the
|
|
28545
|
+
* baseline execution mode) — are authored and
|
|
28259
28546
|
* round-tripped through the `antigravity-cli` permissions override (see
|
|
28260
28547
|
* `AntigravityCliPermissionsOverrideSchema`).
|
|
28261
28548
|
*/
|
|
@@ -28319,10 +28606,10 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28319
28606
|
permissions: mergedPermissions
|
|
28320
28607
|
};
|
|
28321
28608
|
const override = config["antigravity-cli"];
|
|
28322
|
-
|
|
28323
|
-
|
|
28324
|
-
|
|
28325
|
-
|
|
28609
|
+
for (const key of ANTIGRAVITY_CLI_OVERRIDE_KEYS) {
|
|
28610
|
+
const value = override?.[key];
|
|
28611
|
+
if (value !== void 0) merged[key] = value;
|
|
28612
|
+
}
|
|
28326
28613
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
28327
28614
|
return new AntigravityCliPermissions({
|
|
28328
28615
|
outputRoot,
|
|
@@ -28347,10 +28634,26 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28347
28634
|
deny: permissions.deny ?? []
|
|
28348
28635
|
});
|
|
28349
28636
|
const override = {};
|
|
28350
|
-
|
|
28637
|
+
const toolPermission = pickDocumentedValue({
|
|
28638
|
+
key: "toolPermission",
|
|
28639
|
+
value: settings.toolPermission,
|
|
28640
|
+
documented: ANTIGRAVITY_CLI_TOOL_PERMISSIONS
|
|
28641
|
+
});
|
|
28642
|
+
if (toolPermission !== void 0) override.toolPermission = toolPermission;
|
|
28351
28643
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
28352
|
-
|
|
28644
|
+
const artifactReviewPolicy = pickDocumentedValue({
|
|
28645
|
+
key: "artifactReviewPolicy",
|
|
28646
|
+
value: settings.artifactReviewPolicy,
|
|
28647
|
+
documented: ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES
|
|
28648
|
+
});
|
|
28649
|
+
if (artifactReviewPolicy !== void 0) override.artifactReviewPolicy = artifactReviewPolicy;
|
|
28353
28650
|
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
28651
|
+
const agentMode = pickDocumentedValue({
|
|
28652
|
+
key: "agentMode",
|
|
28653
|
+
value: settings.agentMode,
|
|
28654
|
+
documented: ANTIGRAVITY_CLI_AGENT_MODES
|
|
28655
|
+
});
|
|
28656
|
+
if (agentMode !== void 0) override.agentMode = agentMode;
|
|
28354
28657
|
const result = { ...config };
|
|
28355
28658
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
28356
28659
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -29176,6 +29479,119 @@ function deepMergeRecords(base, patch) {
|
|
|
29176
29479
|
}
|
|
29177
29480
|
return merged;
|
|
29178
29481
|
}
|
|
29482
|
+
/**
|
|
29483
|
+
* `sandbox.*` paths Claude Code honors only from user settings, managed settings
|
|
29484
|
+
* and the `--settings` CLI flag. Written into a project `.claude/settings.json`
|
|
29485
|
+
* they are silently ignored, so a committed file would read as though it
|
|
29486
|
+
* enforced a sandbox policy while doing nothing — security-relevant for
|
|
29487
|
+
* `network.strictAllowlist` and the credential-masking keys in particular.
|
|
29488
|
+
* Generation therefore drops them at project scope with a per-key warning and
|
|
29489
|
+
* emits them only under `--global`.
|
|
29490
|
+
*
|
|
29491
|
+
* Deliberately NOT listed:
|
|
29492
|
+
* - `bwrapPath` / `socatPath`: v2.1.232 added them to the managed-settings
|
|
29493
|
+
* approval dialog, which is a consent prompt, not a project-scope rejection.
|
|
29494
|
+
* - `credentials.envVars` / `credentials.files`: the ignored-at-project-scope
|
|
29495
|
+
* unit is the individual entry's mode, not the settings key, and the same
|
|
29496
|
+
* lists carry `deny` entries that project settings *do* honor — dropping a
|
|
29497
|
+
* whole list would remove real restrictions. `stripProjectIgnoredMaskEntries`
|
|
29498
|
+
* filters those lists per entry instead.
|
|
29499
|
+
*
|
|
29500
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
29501
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md — v2.1.232 scoped `sandbox.ripgrep`
|
|
29502
|
+
*/
|
|
29503
|
+
const CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS = [
|
|
29504
|
+
["filesystem", "disabled"],
|
|
29505
|
+
["network", "strictAllowlist"],
|
|
29506
|
+
["network", "tlsTerminate"],
|
|
29507
|
+
["credentials", "allowPlaintextInject"],
|
|
29508
|
+
["credentials", "awsPairs"],
|
|
29509
|
+
["credentials", "sigv4"],
|
|
29510
|
+
["allowAppleEvents"],
|
|
29511
|
+
["ripgrep"]
|
|
29512
|
+
];
|
|
29513
|
+
/**
|
|
29514
|
+
* Copy of the authored `sandbox` override with the user/managed-only paths
|
|
29515
|
+
* removed, warning once per dropped path. Only the override copy is filtered —
|
|
29516
|
+
* a value already hand-written in the target file is left untouched, matching
|
|
29517
|
+
* the `qwencode` `security.allowPrivateNetworkHooks` precedent.
|
|
29518
|
+
*/
|
|
29519
|
+
function stripGlobalOnlySandboxPaths({ sandbox, relativeFilePath, logger }) {
|
|
29520
|
+
const filtered = structuredClone(sandbox);
|
|
29521
|
+
for (const path of CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS) {
|
|
29522
|
+
const leaf = path.at(-1);
|
|
29523
|
+
if (leaf === void 0) continue;
|
|
29524
|
+
const parentPath = path.slice(0, -1);
|
|
29525
|
+
let parent = filtered;
|
|
29526
|
+
for (const segment of parentPath) {
|
|
29527
|
+
const next = parent[segment];
|
|
29528
|
+
if (!isPlainRecord(next)) {
|
|
29529
|
+
parent = {};
|
|
29530
|
+
break;
|
|
29531
|
+
}
|
|
29532
|
+
parent = next;
|
|
29533
|
+
}
|
|
29534
|
+
if (parent[leaf] === void 0) continue;
|
|
29535
|
+
delete parent[leaf];
|
|
29536
|
+
const [container] = parentPath;
|
|
29537
|
+
if (container !== void 0 && isPlainRecord(filtered[container])) {
|
|
29538
|
+
if (Object.keys(filtered[container]).length === 0) delete filtered[container];
|
|
29539
|
+
}
|
|
29540
|
+
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.`);
|
|
29541
|
+
}
|
|
29542
|
+
return filtered;
|
|
29543
|
+
}
|
|
29544
|
+
/** The `sandbox.credentials` lists whose entries accept `"mode": "mask"`. */
|
|
29545
|
+
const CLAUDECODE_MASKABLE_CREDENTIAL_LISTS = ["envVars", "files"];
|
|
29546
|
+
/**
|
|
29547
|
+
* Copy of the authored `sandbox` override with the `credentials.envVars` /
|
|
29548
|
+
* `credentials.files` entries whose `mode` is `"mask"` removed, warning once per
|
|
29549
|
+
* list.
|
|
29550
|
+
*
|
|
29551
|
+
* Masking authorizes the sandbox proxy to send the real credential to the listed
|
|
29552
|
+
* hosts, so Claude Code honors it only from user settings, managed settings and
|
|
29553
|
+
* the `--settings` CLI flag; a `mask` entry in a repository's
|
|
29554
|
+
* `.claude/settings.json` is ignored outright. Keeping it would read as though
|
|
29555
|
+
* the credential were masked while nothing protects it — the one project-scope
|
|
29556
|
+
* gap whose consequence is a live credential leaving the sandbox unmasked.
|
|
29557
|
+
*
|
|
29558
|
+
* Filtered per entry rather than per key, because the same lists carry `deny`
|
|
29559
|
+
* entries that project settings *do* honor. `mode` is matched exactly: an entry
|
|
29560
|
+
* Claude Code cannot read as `mask` is not treated as one either (it degrades
|
|
29561
|
+
* such entries to `deny`), so the "reads as masked but isn't" state this guards
|
|
29562
|
+
* against cannot slip through a differently-spelled value.
|
|
29563
|
+
*
|
|
29564
|
+
* Like `stripGlobalOnlySandboxPaths`, only the override copy is filtered — a
|
|
29565
|
+
* value already in the target file is left untouched, which is why the warning
|
|
29566
|
+
* points at it.
|
|
29567
|
+
*
|
|
29568
|
+
* @see https://code.claude.com/docs/en/sandboxing — "`mask` entries … are all
|
|
29569
|
+
* ignored in a repository's `.claude/settings.json` or
|
|
29570
|
+
* `.claude/settings.local.json`"; the file-entry section states the same
|
|
29571
|
+
* settings-source restriction applies there.
|
|
29572
|
+
*/
|
|
29573
|
+
function stripProjectIgnoredMaskEntries({ sandbox, relativeFilePath, logger }) {
|
|
29574
|
+
const credentials = sandbox.credentials;
|
|
29575
|
+
if (!isPlainRecord(credentials)) return sandbox;
|
|
29576
|
+
const filteredCredentials = { ...credentials };
|
|
29577
|
+
let changed = false;
|
|
29578
|
+
for (const listKey of CLAUDECODE_MASKABLE_CREDENTIAL_LISTS) {
|
|
29579
|
+
const list = filteredCredentials[listKey];
|
|
29580
|
+
if (!Array.isArray(list)) continue;
|
|
29581
|
+
const kept = list.filter((entry) => !(isPlainRecord(entry) && entry.mode === "mask"));
|
|
29582
|
+
if (kept.length === list.length) continue;
|
|
29583
|
+
changed = true;
|
|
29584
|
+
const dropped = list.length - kept.length;
|
|
29585
|
+
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.`);
|
|
29586
|
+
if (kept.length === 0) delete filteredCredentials[listKey];
|
|
29587
|
+
else filteredCredentials[listKey] = kept;
|
|
29588
|
+
}
|
|
29589
|
+
if (!changed) return sandbox;
|
|
29590
|
+
const filtered = { ...sandbox };
|
|
29591
|
+
if (Object.keys(filteredCredentials).length === 0) delete filtered.credentials;
|
|
29592
|
+
else filtered.credentials = filteredCredentials;
|
|
29593
|
+
return filtered;
|
|
29594
|
+
}
|
|
29179
29595
|
const CLAUDE_PATH_RULE_ALIASES = {
|
|
29180
29596
|
Write: "Edit",
|
|
29181
29597
|
NotebookEdit: "Edit",
|
|
@@ -29229,7 +29645,7 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29229
29645
|
validate
|
|
29230
29646
|
});
|
|
29231
29647
|
}
|
|
29232
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
|
|
29648
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
29233
29649
|
const paths = ClaudecodePermissions.getSettablePaths();
|
|
29234
29650
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
29235
29651
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -29253,7 +29669,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29253
29669
|
};
|
|
29254
29670
|
}
|
|
29255
29671
|
const overrideSandbox = config.claudecode?.sandbox;
|
|
29256
|
-
if (isPlainRecord(overrideSandbox))
|
|
29672
|
+
if (isPlainRecord(overrideSandbox)) {
|
|
29673
|
+
const scopedSandbox = global ? overrideSandbox : stripProjectIgnoredMaskEntries({
|
|
29674
|
+
sandbox: stripGlobalOnlySandboxPaths({
|
|
29675
|
+
sandbox: overrideSandbox,
|
|
29676
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29677
|
+
logger
|
|
29678
|
+
}),
|
|
29679
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29680
|
+
logger
|
|
29681
|
+
});
|
|
29682
|
+
if (Object.keys(scopedSandbox).length > 0) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, scopedSandbox);
|
|
29683
|
+
}
|
|
29257
29684
|
const managedToolNames = managedClaudeToolNames(config);
|
|
29258
29685
|
const merged = applyPermissions({
|
|
29259
29686
|
settings,
|
|
@@ -31430,7 +31857,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
31430
31857
|
function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
31431
31858
|
const permission = {};
|
|
31432
31859
|
for (const key of GOOSE_PERMISSION_LIST_KEYS) {
|
|
31433
|
-
const toolNames = isStringArray$
|
|
31860
|
+
const toolNames = isStringArray$2(userPermission[key]) ? userPermission[key] : [];
|
|
31434
31861
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
31435
31862
|
for (const toolName of toolNames) {
|
|
31436
31863
|
const category = GOOSE_TO_RULESYNC_TOOL_NAME[toolName] ?? toolName;
|
|
@@ -31723,7 +32150,7 @@ const ACTION_RANK = {
|
|
|
31723
32150
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
31724
32151
|
*/
|
|
31725
32152
|
function unmanagedEntries(existingPermission, key) {
|
|
31726
|
-
return (isStringArray$
|
|
32153
|
+
return (isStringArray$2(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
31727
32154
|
}
|
|
31728
32155
|
/**
|
|
31729
32156
|
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
@@ -31766,9 +32193,9 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
31766
32193
|
* described more than once resolves to the strictest action.
|
|
31767
32194
|
*/
|
|
31768
32195
|
function parseGrokPermissionArrays(permission) {
|
|
31769
|
-
const allow = isStringArray$
|
|
31770
|
-
const deny = isStringArray$
|
|
31771
|
-
const ask = isStringArray$
|
|
32196
|
+
const allow = isStringArray$2(permission.allow) ? permission.allow : void 0;
|
|
32197
|
+
const deny = isStringArray$2(permission.deny) ? permission.deny : void 0;
|
|
32198
|
+
const ask = isStringArray$2(permission.ask) ? permission.ask : void 0;
|
|
31772
32199
|
const rules = Array.isArray(permission[GROKCLI_PERMISSION_RULES_KEY]) ? permission[GROKCLI_PERMISSION_RULES_KEY] : void 0;
|
|
31773
32200
|
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
31774
32201
|
const result = {};
|
|
@@ -31950,20 +32377,20 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
31950
32377
|
const permission = clonePermissionBlock(provenance.permission);
|
|
31951
32378
|
reconcileCommandAllowlist({
|
|
31952
32379
|
permission,
|
|
31953
|
-
commandAllowlist: isStringArray$
|
|
32380
|
+
commandAllowlist: isStringArray$2(config.command_allowlist) ? config.command_allowlist : []
|
|
31954
32381
|
});
|
|
31955
32382
|
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
31956
32383
|
reconcileNativeDenies({
|
|
31957
32384
|
permission,
|
|
31958
32385
|
category: "bash",
|
|
31959
|
-
patterns: isStringArray$
|
|
32386
|
+
patterns: isStringArray$2(approvals.deny) ? approvals.deny : []
|
|
31960
32387
|
});
|
|
31961
32388
|
const security = isRecord$1(config.security) ? config.security : {};
|
|
31962
32389
|
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
31963
32390
|
reconcileNativeDenies({
|
|
31964
32391
|
permission,
|
|
31965
32392
|
category: "webfetch",
|
|
31966
|
-
patterns: websiteBlocklist.enabled === true && isStringArray$
|
|
32393
|
+
patterns: websiteBlocklist.enabled === true && isStringArray$2(websiteBlocklist.domains) ? websiteBlocklist.domains : []
|
|
31967
32394
|
});
|
|
31968
32395
|
removeEmptyCategories(permission);
|
|
31969
32396
|
const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
|
|
@@ -33151,7 +33578,7 @@ function extractKiroOverride(toolsSettings) {
|
|
|
33151
33578
|
if (Object.keys(overrideToolsSettings).length === 0) return void 0;
|
|
33152
33579
|
return { toolsSettings: overrideToolsSettings };
|
|
33153
33580
|
}
|
|
33154
|
-
function asStringArray(value) {
|
|
33581
|
+
function asStringArray$1(value) {
|
|
33155
33582
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
33156
33583
|
}
|
|
33157
33584
|
/**
|
|
@@ -33161,8 +33588,8 @@ function asStringArray(value) {
|
|
|
33161
33588
|
*/
|
|
33162
33589
|
function rulesFromArrays(settings, allowKey, denyKey) {
|
|
33163
33590
|
const rules = {};
|
|
33164
|
-
for (const pattern of asStringArray(settings[allowKey])) rules[pattern] = "allow";
|
|
33165
|
-
for (const pattern of asStringArray(settings[denyKey])) rules[pattern] = "deny";
|
|
33591
|
+
for (const pattern of asStringArray$1(settings[allowKey])) rules[pattern] = "allow";
|
|
33592
|
+
for (const pattern of asStringArray$1(settings[denyKey])) rules[pattern] = "deny";
|
|
33166
33593
|
return rules;
|
|
33167
33594
|
}
|
|
33168
33595
|
//#endregion
|
|
@@ -33391,6 +33818,115 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
33391
33818
|
}
|
|
33392
33819
|
};
|
|
33393
33820
|
//#endregion
|
|
33821
|
+
//#region src/features/permissions/pi-permissions.ts
|
|
33822
|
+
/** The one `settings.json` key this feature owns. */
|
|
33823
|
+
const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
33824
|
+
/**
|
|
33825
|
+
* Permissions generator for Pi Coding Agent.
|
|
33826
|
+
*
|
|
33827
|
+
* Pi exposes no allow/ask/deny rule surface, so no canonical permission
|
|
33828
|
+
* category maps onto it. Its one repository-syncable tool gate is `defaultTools` —
|
|
33829
|
+
* the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
|
|
33830
|
+
* permissions override.
|
|
33831
|
+
*
|
|
33832
|
+
* Scope semantics are the **opposite** of the union-merge most targets use:
|
|
33833
|
+
* a project `defaultTools` array *replaces* the global array rather than adding
|
|
33834
|
+
* to it, so the two scopes are written independently and never combined.
|
|
33835
|
+
* Project scope writes `.pi/settings.json`; global scope writes
|
|
33836
|
+
* `~/.pi/agent/settings.json`.
|
|
33837
|
+
*
|
|
33838
|
+
* The file is hand-edited and holds many unrelated keys (`theme`,
|
|
33839
|
+
* `defaultModel`, `packages`, `sessionDir`, ...), so writes go through the
|
|
33840
|
+
* shared-config gateway with `defaultTools` as the only owned key, and the file
|
|
33841
|
+
* is never deleted.
|
|
33842
|
+
*
|
|
33843
|
+
* @see https://pi.dev/docs/latest/settings
|
|
33844
|
+
*/
|
|
33845
|
+
var PiPermissions = class PiPermissions extends ToolPermissions {
|
|
33846
|
+
constructor(params) {
|
|
33847
|
+
super({
|
|
33848
|
+
...params,
|
|
33849
|
+
fileContent: params.fileContent ?? "{}"
|
|
33850
|
+
});
|
|
33851
|
+
}
|
|
33852
|
+
/** `settings.json` holds unrelated user settings, so it must not be deleted. */
|
|
33853
|
+
isDeletable() {
|
|
33854
|
+
return false;
|
|
33855
|
+
}
|
|
33856
|
+
static getSettablePaths({ global = false } = {}) {
|
|
33857
|
+
return {
|
|
33858
|
+
relativeDirPath: global ? PI_AGENT_DIR_PATH : ".pi",
|
|
33859
|
+
relativeFilePath: PI_SETTINGS_FILE_NAME
|
|
33860
|
+
};
|
|
33861
|
+
}
|
|
33862
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
33863
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33864
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
33865
|
+
return new PiPermissions({
|
|
33866
|
+
outputRoot,
|
|
33867
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33868
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33869
|
+
fileContent,
|
|
33870
|
+
validate,
|
|
33871
|
+
global
|
|
33872
|
+
});
|
|
33873
|
+
}
|
|
33874
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
33875
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33876
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
33877
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
33878
|
+
const defaultTools = rulesyncPermissions.getJson().pi?.defaultTools;
|
|
33879
|
+
const patch = {};
|
|
33880
|
+
if (defaultTools !== void 0) patch[DEFAULT_TOOLS_KEY] = defaultTools;
|
|
33881
|
+
return new PiPermissions({
|
|
33882
|
+
outputRoot,
|
|
33883
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33884
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33885
|
+
fileContent: applySharedConfigPatch({
|
|
33886
|
+
fileKey: sharedConfigFileKey(paths),
|
|
33887
|
+
feature: "permissions",
|
|
33888
|
+
existingContent,
|
|
33889
|
+
patch,
|
|
33890
|
+
filePath
|
|
33891
|
+
}),
|
|
33892
|
+
validate: true,
|
|
33893
|
+
global
|
|
33894
|
+
});
|
|
33895
|
+
}
|
|
33896
|
+
toRulesyncPermissions() {
|
|
33897
|
+
const defaultTools = this.parseSettings()[DEFAULT_TOOLS_KEY];
|
|
33898
|
+
const config = { permission: {} };
|
|
33899
|
+
if (Array.isArray(defaultTools) && defaultTools.every((tool) => typeof tool === "string")) config.pi = { [DEFAULT_TOOLS_KEY]: defaultTools };
|
|
33900
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
33901
|
+
}
|
|
33902
|
+
parseSettings() {
|
|
33903
|
+
try {
|
|
33904
|
+
return parseSharedConfig({
|
|
33905
|
+
format: "json",
|
|
33906
|
+
fileContent: this.getFileContent()
|
|
33907
|
+
});
|
|
33908
|
+
} catch (error) {
|
|
33909
|
+
throw new Error(`Failed to parse Pi settings in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
33910
|
+
}
|
|
33911
|
+
}
|
|
33912
|
+
validate() {
|
|
33913
|
+
return {
|
|
33914
|
+
success: true,
|
|
33915
|
+
error: null
|
|
33916
|
+
};
|
|
33917
|
+
}
|
|
33918
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
33919
|
+
return new PiPermissions({
|
|
33920
|
+
outputRoot,
|
|
33921
|
+
relativeDirPath,
|
|
33922
|
+
relativeFilePath,
|
|
33923
|
+
fileContent: "{}",
|
|
33924
|
+
validate: false,
|
|
33925
|
+
global
|
|
33926
|
+
});
|
|
33927
|
+
}
|
|
33928
|
+
};
|
|
33929
|
+
//#endregion
|
|
33394
33930
|
//#region src/features/permissions/qwencode-permissions.ts
|
|
33395
33931
|
/**
|
|
33396
33932
|
* Qwen Code uses a settings.json file in `.qwen/` (project) or `~/.qwen/` (global).
|
|
@@ -34454,7 +34990,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
34454
34990
|
permission[category] ??= {};
|
|
34455
34991
|
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
34456
34992
|
}
|
|
34457
|
-
if (isStringArray$
|
|
34993
|
+
if (isStringArray$2(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
34458
34994
|
permission.read ??= {};
|
|
34459
34995
|
permission.read[path] ??= "allow";
|
|
34460
34996
|
}
|
|
@@ -35431,8 +35967,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
35431
35967
|
const executionProfiles = isRecord$1(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
35432
35968
|
const defaultProfile = executionProfiles && isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
35433
35969
|
const config = convertWarpToRulesyncPermissions({
|
|
35434
|
-
allow: defaultProfile ? isStringArray$
|
|
35435
|
-
deny: defaultProfile ? isStringArray$
|
|
35970
|
+
allow: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$2(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
35971
|
+
deny: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$2(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
35436
35972
|
});
|
|
35437
35973
|
const warpOverride = {};
|
|
35438
35974
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
@@ -35965,6 +36501,181 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
35965
36501
|
}
|
|
35966
36502
|
};
|
|
35967
36503
|
//#endregion
|
|
36504
|
+
//#region src/constants/zoocode-paths.ts
|
|
36505
|
+
/**
|
|
36506
|
+
* Zoo Code is a VS Code extension, so its committable command allow/deny lists
|
|
36507
|
+
* are workspace settings rather than files in the `.roo/` agent-asset tree that
|
|
36508
|
+
* the other Zoo Code features write.
|
|
36509
|
+
*
|
|
36510
|
+
* `zoo-code.allowedCommands` / `zoo-code.deniedCommands` are contributed with no
|
|
36511
|
+
* `scope`, which in VS Code means `window` scope — settable in a workspace's
|
|
36512
|
+
* `.vscode/settings.json` — and `ClineProvider.mergeCommandLists()` unions the
|
|
36513
|
+
* workspace values into the effective auto-approval lists.
|
|
36514
|
+
*
|
|
36515
|
+
* The `zoo-code.*` namespace is Zoo-era (the v3.74.0 rebrand); the archived Roo
|
|
36516
|
+
* Code lineage spelled the same settings `roo-cline.*`, so this surface is
|
|
36517
|
+
* deliberately not shared with the `roo` target.
|
|
36518
|
+
*
|
|
36519
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/package.json
|
|
36520
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/core/webview/ClineProvider.ts
|
|
36521
|
+
*/
|
|
36522
|
+
const ZOOCODE_VSCODE_SETTINGS_DIR = ".vscode";
|
|
36523
|
+
const ZOOCODE_VSCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
36524
|
+
const ZOOCODE_ALLOWED_COMMANDS_KEY = "zoo-code.allowedCommands";
|
|
36525
|
+
const ZOOCODE_DENIED_COMMANDS_KEY = "zoo-code.deniedCommands";
|
|
36526
|
+
//#endregion
|
|
36527
|
+
//#region src/features/permissions/zoocode-permissions.ts
|
|
36528
|
+
/**
|
|
36529
|
+
* The canonical category Zoo Code's command lists correspond to. Zoo Code gates
|
|
36530
|
+
* terminal command execution and nothing else through these settings, so only
|
|
36531
|
+
* `bash` maps; `read`/`write`/`edit`/`webfetch` have no workspace-settable
|
|
36532
|
+
* counterpart in the extension's contributions.
|
|
36533
|
+
*/
|
|
36534
|
+
const COMMAND_CATEGORY = "bash";
|
|
36535
|
+
function asStringArray(value) {
|
|
36536
|
+
if (!Array.isArray(value)) return [];
|
|
36537
|
+
return value.filter((entry) => typeof entry === "string");
|
|
36538
|
+
}
|
|
36539
|
+
/**
|
|
36540
|
+
* Split one canonical category's rules into Zoo Code's two command lists.
|
|
36541
|
+
*
|
|
36542
|
+
* Zoo Code matches these entries as command **prefixes**: a command runs
|
|
36543
|
+
* without a confirmation prompt when it starts with an `allowedCommands` entry,
|
|
36544
|
+
* and is refused outright when it starts with a `deniedCommands` entry (deny
|
|
36545
|
+
* wins). `ask` is represented by listing the pattern in neither list, which
|
|
36546
|
+
* leaves Zoo Code's default approval prompt in charge.
|
|
36547
|
+
*
|
|
36548
|
+
* Each list is `undefined` when it would be empty, so the key is retracted from
|
|
36549
|
+
* the settings file rather than written as an empty array — an empty
|
|
36550
|
+
* `allowedCommands` and an absent one mean the same thing to Zoo Code, and the
|
|
36551
|
+
* absent form leaves no rulesync residue behind.
|
|
36552
|
+
*/
|
|
36553
|
+
function buildCommandLists(rules) {
|
|
36554
|
+
const allowed = [];
|
|
36555
|
+
const denied = [];
|
|
36556
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow") allowed.push(pattern);
|
|
36557
|
+
else if (action === "deny") denied.push(pattern);
|
|
36558
|
+
return {
|
|
36559
|
+
allowed: allowed.length > 0 ? allowed : void 0,
|
|
36560
|
+
denied: denied.length > 0 ? denied : void 0
|
|
36561
|
+
};
|
|
36562
|
+
}
|
|
36563
|
+
/**
|
|
36564
|
+
* Permissions generator for Zoo Code.
|
|
36565
|
+
*
|
|
36566
|
+
* Zoo Code has no policy file in its `.roo/` tree: the committable command
|
|
36567
|
+
* allow/deny lists are VS Code workspace settings
|
|
36568
|
+
* (`zoo-code.allowedCommands` / `zoo-code.deniedCommands` in
|
|
36569
|
+
* `.vscode/settings.json`), which `ClineProvider.mergeCommandLists()` unions
|
|
36570
|
+
* into the lists the auto-approval decision reads. That file is a
|
|
36571
|
+
* general-purpose workspace settings file with many unrelated keys, so reads
|
|
36572
|
+
* and writes merge into the existing JSONC (touching only the two managed keys)
|
|
36573
|
+
* and the file is never deleted.
|
|
36574
|
+
*
|
|
36575
|
+
* Only project scope is modeled: VS Code's user-scope `settings.json` lives at
|
|
36576
|
+
* a platform-dependent path outside rulesync's home-relative global model.
|
|
36577
|
+
*
|
|
36578
|
+
* The `roo` target deliberately does not get this adapter. The settings
|
|
36579
|
+
* namespace is Zoo-era (`roo-cline.*` before the v3.74.0 rebrand), and Roo Code
|
|
36580
|
+
* is EOL with its repository archived, so emitting `zoo-code.*` keys for a
|
|
36581
|
+
* `--targets roo` generate would write settings that Roo itself never reads.
|
|
36582
|
+
*
|
|
36583
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/package.json
|
|
36584
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/core/auto-approval/commands.ts
|
|
36585
|
+
*/
|
|
36586
|
+
var ZoocodePermissions = class ZoocodePermissions extends ToolPermissions {
|
|
36587
|
+
constructor(params) {
|
|
36588
|
+
super({
|
|
36589
|
+
...params,
|
|
36590
|
+
fileContent: params.fileContent ?? "{}"
|
|
36591
|
+
});
|
|
36592
|
+
}
|
|
36593
|
+
/**
|
|
36594
|
+
* `.vscode/settings.json` is a user-managed workspace file with unrelated
|
|
36595
|
+
* settings, so it must not be deleted.
|
|
36596
|
+
*/
|
|
36597
|
+
isDeletable() {
|
|
36598
|
+
return false;
|
|
36599
|
+
}
|
|
36600
|
+
static getSettablePaths(_options = {}) {
|
|
36601
|
+
return {
|
|
36602
|
+
relativeDirPath: ZOOCODE_VSCODE_SETTINGS_DIR,
|
|
36603
|
+
relativeFilePath: ZOOCODE_VSCODE_SETTINGS_FILE_NAME
|
|
36604
|
+
};
|
|
36605
|
+
}
|
|
36606
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
36607
|
+
const paths = ZoocodePermissions.getSettablePaths();
|
|
36608
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
36609
|
+
return new ZoocodePermissions({
|
|
36610
|
+
outputRoot,
|
|
36611
|
+
relativeDirPath: paths.relativeDirPath,
|
|
36612
|
+
relativeFilePath: paths.relativeFilePath,
|
|
36613
|
+
fileContent,
|
|
36614
|
+
validate
|
|
36615
|
+
});
|
|
36616
|
+
}
|
|
36617
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions }) {
|
|
36618
|
+
const paths = ZoocodePermissions.getSettablePaths();
|
|
36619
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
36620
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
36621
|
+
const rules = rulesyncPermissions.getJson().permission[COMMAND_CATEGORY];
|
|
36622
|
+
const patch = {};
|
|
36623
|
+
if (rules !== void 0) {
|
|
36624
|
+
const { allowed, denied } = buildCommandLists(rules);
|
|
36625
|
+
patch[ZOOCODE_ALLOWED_COMMANDS_KEY] = allowed;
|
|
36626
|
+
patch[ZOOCODE_DENIED_COMMANDS_KEY] = denied;
|
|
36627
|
+
}
|
|
36628
|
+
return new ZoocodePermissions({
|
|
36629
|
+
outputRoot,
|
|
36630
|
+
relativeDirPath: paths.relativeDirPath,
|
|
36631
|
+
relativeFilePath: paths.relativeFilePath,
|
|
36632
|
+
fileContent: applySharedConfigPatch({
|
|
36633
|
+
fileKey: sharedConfigFileKey(paths),
|
|
36634
|
+
feature: "permissions",
|
|
36635
|
+
existingContent,
|
|
36636
|
+
patch,
|
|
36637
|
+
filePath
|
|
36638
|
+
}),
|
|
36639
|
+
validate: true
|
|
36640
|
+
});
|
|
36641
|
+
}
|
|
36642
|
+
toRulesyncPermissions() {
|
|
36643
|
+
let settings;
|
|
36644
|
+
try {
|
|
36645
|
+
settings = parseSharedConfig({
|
|
36646
|
+
format: "jsonc",
|
|
36647
|
+
fileContent: this.getFileContent() || "{}",
|
|
36648
|
+
filePath: (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath()),
|
|
36649
|
+
invalidRootPolicy: "error",
|
|
36650
|
+
jsoncParseErrors: "error"
|
|
36651
|
+
});
|
|
36652
|
+
} catch (error) {
|
|
36653
|
+
throw new Error(`Failed to parse Zoo Code VS Code settings in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
36654
|
+
}
|
|
36655
|
+
const rules = {};
|
|
36656
|
+
for (const pattern of asStringArray(settings[ZOOCODE_ALLOWED_COMMANDS_KEY])) rules[pattern] = "allow";
|
|
36657
|
+
for (const pattern of asStringArray(settings[ZOOCODE_DENIED_COMMANDS_KEY])) rules[pattern] = "deny";
|
|
36658
|
+
const permission = {};
|
|
36659
|
+
if (Object.keys(rules).length > 0) permission[COMMAND_CATEGORY] = rules;
|
|
36660
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
36661
|
+
}
|
|
36662
|
+
validate() {
|
|
36663
|
+
return {
|
|
36664
|
+
success: true,
|
|
36665
|
+
error: null
|
|
36666
|
+
};
|
|
36667
|
+
}
|
|
36668
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
36669
|
+
return new ZoocodePermissions({
|
|
36670
|
+
outputRoot,
|
|
36671
|
+
relativeDirPath,
|
|
36672
|
+
relativeFilePath,
|
|
36673
|
+
fileContent: "{}",
|
|
36674
|
+
validate: false
|
|
36675
|
+
});
|
|
36676
|
+
}
|
|
36677
|
+
};
|
|
36678
|
+
//#endregion
|
|
35968
36679
|
//#region src/features/permissions/permissions-processor.ts
|
|
35969
36680
|
const PermissionsProcessorToolTargetSchema = zod_mini.z.enum(permissionsProcessorToolTargetTuple);
|
|
35970
36681
|
const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
@@ -36144,6 +36855,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
36144
36855
|
supportsImport: true
|
|
36145
36856
|
}
|
|
36146
36857
|
}],
|
|
36858
|
+
["pi", {
|
|
36859
|
+
class: PiPermissions,
|
|
36860
|
+
meta: {
|
|
36861
|
+
supportsProject: true,
|
|
36862
|
+
supportsGlobal: true,
|
|
36863
|
+
supportsImport: true
|
|
36864
|
+
}
|
|
36865
|
+
}],
|
|
36147
36866
|
["qwencode", {
|
|
36148
36867
|
class: QwencodePermissions,
|
|
36149
36868
|
meta: {
|
|
@@ -36199,6 +36918,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
36199
36918
|
supportsGlobal: true,
|
|
36200
36919
|
supportsImport: true
|
|
36201
36920
|
}
|
|
36921
|
+
}],
|
|
36922
|
+
["zoocode", {
|
|
36923
|
+
class: ZoocodePermissions,
|
|
36924
|
+
meta: {
|
|
36925
|
+
supportsProject: true,
|
|
36926
|
+
supportsGlobal: false,
|
|
36927
|
+
supportsImport: true
|
|
36928
|
+
}
|
|
36202
36929
|
}]
|
|
36203
36930
|
]);
|
|
36204
36931
|
var PermissionsProcessor = class extends FeatureProcessor {
|
|
@@ -38455,6 +39182,7 @@ const CursorSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
38455
39182
|
description: zod_mini.z.string(),
|
|
38456
39183
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
38457
39184
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
39185
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38458
39186
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
38459
39187
|
});
|
|
38460
39188
|
/**
|
|
@@ -38509,6 +39237,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38509
39237
|
const cursorSection = {
|
|
38510
39238
|
...frontmatter.paths !== void 0 && { paths: frontmatter.paths },
|
|
38511
39239
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
39240
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
38512
39241
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
38513
39242
|
};
|
|
38514
39243
|
const rulesyncFrontmatter = {
|
|
@@ -38536,11 +39265,16 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38536
39265
|
rootFrontmatter: rulesyncFrontmatter,
|
|
38537
39266
|
section: cursorSection
|
|
38538
39267
|
});
|
|
39268
|
+
const resolvedUserInvocable = resolveUserInvocable({
|
|
39269
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
39270
|
+
section: cursorSection
|
|
39271
|
+
});
|
|
38539
39272
|
const cursorFrontmatter = {
|
|
38540
39273
|
name: rulesyncFrontmatter.name,
|
|
38541
39274
|
description: rulesyncFrontmatter.description,
|
|
38542
39275
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
38543
39276
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
39277
|
+
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
38544
39278
|
...cursorSection?.metadata !== void 0 && { metadata: cursorSection.metadata }
|
|
38545
39279
|
};
|
|
38546
39280
|
return new CursorSkill({
|
|
@@ -46543,6 +47277,23 @@ var TaktSubagent = class TaktSubagent extends ToolSubagent {
|
|
|
46543
47277
|
};
|
|
46544
47278
|
//#endregion
|
|
46545
47279
|
//#region src/features/subagents/vibe-subagent.ts
|
|
47280
|
+
/**
|
|
47281
|
+
* Where Vibe reads a custom system prompt from: `.vibe/prompts/<id>.md`
|
|
47282
|
+
* (project) and `~/.vibe/prompts/<id>.md` (user), with the custom directories
|
|
47283
|
+
* winning over the builtins. The id must be a bare file name with no path
|
|
47284
|
+
* separators.
|
|
47285
|
+
* @see vibe/core/prompts/__init__.py
|
|
47286
|
+
*/
|
|
47287
|
+
const VIBE_PROMPTS_DIR_PATH = (0, node_path.join)(".vibe", "prompts");
|
|
47288
|
+
/**
|
|
47289
|
+
* Vibe resolves a `system_prompt_id` by file name, and
|
|
47290
|
+
* `VibeConfigSchema._check_system_prompt` evaluates it during validation — an
|
|
47291
|
+
* unresolvable id raises and the agent is dropped at discovery. The id must
|
|
47292
|
+
* therefore stay a bare slug with no path separators.
|
|
47293
|
+
*/
|
|
47294
|
+
function promptSlug(relativeFilePath) {
|
|
47295
|
+
return (0, node_path.basename)(relativeFilePath).replace(/\.(md|toml)$/, "").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
47296
|
+
}
|
|
46546
47297
|
const VibeSubagentTomlSchema = zod_mini.z.looseObject({
|
|
46547
47298
|
agent_type: zod_mini.z.enum(["agent", "subagent"]),
|
|
46548
47299
|
display_name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -46559,14 +47310,22 @@ const VibeSubagentTomlSchema = zod_mini.z.looseObject({
|
|
|
46559
47310
|
});
|
|
46560
47311
|
var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
46561
47312
|
body;
|
|
46562
|
-
|
|
46563
|
-
|
|
47313
|
+
promptFile;
|
|
47314
|
+
resolvedSystemPrompt;
|
|
47315
|
+
constructor({ body, promptFile = false, resolvedSystemPrompt, ...rest }) {
|
|
47316
|
+
if (rest.validate !== false && !promptFile) try {
|
|
46564
47317
|
VibeSubagentTomlSchema.parse(smol_toml.parse(body));
|
|
46565
47318
|
} catch (error) {
|
|
46566
47319
|
throw new Error(`Invalid TOML in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
46567
47320
|
}
|
|
46568
47321
|
super({ ...rest });
|
|
46569
47322
|
this.body = body;
|
|
47323
|
+
this.promptFile = promptFile;
|
|
47324
|
+
this.resolvedSystemPrompt = resolvedSystemPrompt;
|
|
47325
|
+
}
|
|
47326
|
+
/** True for the companion `.vibe/prompts/<id>.md` file. */
|
|
47327
|
+
isPromptFile() {
|
|
47328
|
+
return this.promptFile;
|
|
46570
47329
|
}
|
|
46571
47330
|
static getSettablePaths(_options = {}) {
|
|
46572
47331
|
return { relativeDirPath: (0, node_path.join)(".vibe", "agents") };
|
|
@@ -46581,7 +47340,9 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46581
47340
|
} catch (error) {
|
|
46582
47341
|
throw new Error(`Failed to parse TOML in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
46583
47342
|
}
|
|
46584
|
-
const { system_prompt, description, display_name, ...vibeSection } = parsed;
|
|
47343
|
+
const { system_prompt, system_prompt_id, description, display_name, ...vibeSection } = parsed;
|
|
47344
|
+
const resolvedBody = this.resolvedSystemPrompt ?? system_prompt;
|
|
47345
|
+
const unresolvedPromptId = this.resolvedSystemPrompt === void 0 ? system_prompt_id : void 0;
|
|
46585
47346
|
const fileStem = (0, node_path.basename)(this.getRelativeFilePath(), ".toml");
|
|
46586
47347
|
const rulesyncFrontmatter = {
|
|
46587
47348
|
targets: ["vibe"],
|
|
@@ -46590,13 +47351,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46590
47351
|
vibe: {
|
|
46591
47352
|
...display_name !== void 0 && { display_name },
|
|
46592
47353
|
...description !== void 0 && { description },
|
|
47354
|
+
...unresolvedPromptId !== void 0 && { system_prompt_id: unresolvedPromptId },
|
|
46593
47355
|
...vibeSection
|
|
46594
47356
|
}
|
|
46595
47357
|
};
|
|
46596
47358
|
return new RulesyncSubagent({
|
|
46597
47359
|
outputRoot: this.outputRoot,
|
|
46598
47360
|
frontmatter: rulesyncFrontmatter,
|
|
46599
|
-
body:
|
|
47361
|
+
body: resolvedBody ?? "",
|
|
46600
47362
|
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
46601
47363
|
relativeFilePath: this.getRelativeFilePath().replace(/\.toml$/, ".md"),
|
|
46602
47364
|
validate: true
|
|
@@ -46611,12 +47373,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46611
47373
|
"description",
|
|
46612
47374
|
"system_prompt"
|
|
46613
47375
|
]);
|
|
47376
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47377
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
46614
47378
|
const tomlObj = {
|
|
46615
47379
|
agent_type: rawSection.agent_type === "agent" ? "agent" : "subagent",
|
|
46616
47380
|
display_name: typeof rawSection.display_name === "string" ? rawSection.display_name : frontmatter.name,
|
|
46617
47381
|
...frontmatter.description ? { description: frontmatter.description } : {},
|
|
46618
|
-
...
|
|
46619
|
-
...
|
|
47382
|
+
...vibeSection,
|
|
47383
|
+
...promptBody ? { system_prompt_id: slug } : {}
|
|
46620
47384
|
};
|
|
46621
47385
|
const body = smol_toml.stringify(tomlObj);
|
|
46622
47386
|
const paths = this.getSettablePaths({ global });
|
|
@@ -46631,6 +47395,36 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46631
47395
|
global
|
|
46632
47396
|
});
|
|
46633
47397
|
}
|
|
47398
|
+
/**
|
|
47399
|
+
* Emits the agent TOML plus its companion prompt file. The prompt file MUST
|
|
47400
|
+
* be written together with the id: `VibeConfigSchema._check_system_prompt`
|
|
47401
|
+
* evaluates the property during validation, so an unresolvable
|
|
47402
|
+
* `system_prompt_id` raises and `AgentRegistry._try_load` drops the agent
|
|
47403
|
+
* with a warning.
|
|
47404
|
+
*/
|
|
47405
|
+
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false }) {
|
|
47406
|
+
return rulesyncSubagents.flatMap((rulesyncSubagent) => {
|
|
47407
|
+
const agentFile = VibeSubagent.fromRulesyncSubagent({
|
|
47408
|
+
outputRoot,
|
|
47409
|
+
relativeDirPath: VibeSubagent.getSettablePaths({ global }).relativeDirPath,
|
|
47410
|
+
rulesyncSubagent,
|
|
47411
|
+
global
|
|
47412
|
+
});
|
|
47413
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47414
|
+
if (!promptBody) return [agentFile];
|
|
47415
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
47416
|
+
return [agentFile, new VibeSubagent({
|
|
47417
|
+
outputRoot,
|
|
47418
|
+
body: promptBody,
|
|
47419
|
+
relativeDirPath: VIBE_PROMPTS_DIR_PATH,
|
|
47420
|
+
relativeFilePath: `${slug}.md`,
|
|
47421
|
+
fileContent: promptBody,
|
|
47422
|
+
promptFile: true,
|
|
47423
|
+
validate: false,
|
|
47424
|
+
global
|
|
47425
|
+
})];
|
|
47426
|
+
});
|
|
47427
|
+
}
|
|
46634
47428
|
validate() {
|
|
46635
47429
|
try {
|
|
46636
47430
|
VibeSubagentTomlSchema.parse(smol_toml.parse(this.body));
|
|
@@ -46655,6 +47449,10 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46655
47449
|
const paths = this.getSettablePaths({ global });
|
|
46656
47450
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
46657
47451
|
const fileContent = await readFileContent(filePath);
|
|
47452
|
+
const resolvedSystemPrompt = await VibeSubagent.readSystemPrompt({
|
|
47453
|
+
outputRoot,
|
|
47454
|
+
fileContent
|
|
47455
|
+
});
|
|
46658
47456
|
const subagent = new VibeSubagent({
|
|
46659
47457
|
outputRoot,
|
|
46660
47458
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -46662,7 +47460,8 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46662
47460
|
body: fileContent.trim(),
|
|
46663
47461
|
fileContent,
|
|
46664
47462
|
validate,
|
|
46665
|
-
global
|
|
47463
|
+
global,
|
|
47464
|
+
...resolvedSystemPrompt !== void 0 && { resolvedSystemPrompt }
|
|
46666
47465
|
});
|
|
46667
47466
|
if (validate) {
|
|
46668
47467
|
const result = subagent.validate();
|
|
@@ -46670,6 +47469,21 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46670
47469
|
}
|
|
46671
47470
|
return subagent;
|
|
46672
47471
|
}
|
|
47472
|
+
/**
|
|
47473
|
+
* Read `.vibe/prompts/<id>.md` for an agent that names a `system_prompt_id`.
|
|
47474
|
+
* Returns `undefined` when there is no id or the file is missing, in which
|
|
47475
|
+
* case import falls back to the legacy `system_prompt` key.
|
|
47476
|
+
*/
|
|
47477
|
+
static async readSystemPrompt({ outputRoot, fileContent }) {
|
|
47478
|
+
let promptId;
|
|
47479
|
+
try {
|
|
47480
|
+
promptId = VibeSubagentTomlSchema.parse(smol_toml.parse(fileContent)).system_prompt_id;
|
|
47481
|
+
} catch {
|
|
47482
|
+
return;
|
|
47483
|
+
}
|
|
47484
|
+
if (typeof promptId !== "string" || promptId === "" || /[\\/]/.test(promptId)) return;
|
|
47485
|
+
return await readFileContentOrNull((0, node_path.join)(outputRoot, VIBE_PROMPTS_DIR_PATH, `${promptId}.md`)) ?? void 0;
|
|
47486
|
+
}
|
|
46673
47487
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
46674
47488
|
return new VibeSubagent({
|
|
46675
47489
|
outputRoot,
|
|
@@ -46712,7 +47526,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46712
47526
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
46713
47527
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
46714
47528
|
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
46715
|
-
if (isStringArray$
|
|
47529
|
+
if (isStringArray$2(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
46716
47530
|
return mode;
|
|
46717
47531
|
}
|
|
46718
47532
|
toRulesyncSubagents() {
|
|
@@ -46724,7 +47538,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46724
47538
|
targets: ["zoocode"],
|
|
46725
47539
|
roo: restRooSection
|
|
46726
47540
|
};
|
|
46727
|
-
if (isStringArray$
|
|
47541
|
+
if (isStringArray$2(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
46728
47542
|
return new RulesyncSubagent({
|
|
46729
47543
|
outputRoot: ".",
|
|
46730
47544
|
frontmatter: rebuilt,
|
|
@@ -49627,8 +50441,13 @@ const STRATEGIES = [
|
|
|
49627
50441
|
* file per rule under `.devin/rules/*.md` (Devin Desktop Cascade) with YAML
|
|
49628
50442
|
* frontmatter carrying a `trigger` (always_on | glob | manual | model_decision)
|
|
49629
50443
|
* plus companion `globs`/`description` fields.
|
|
49630
|
-
* - Global scope:
|
|
49631
|
-
*
|
|
50444
|
+
* - Global scope: mirrors the project layout. The root rule is a single
|
|
50445
|
+
* plain-markdown, always-on file (no frontmatter) at
|
|
50446
|
+
* `~/.config/devin/AGENTS.md`; non-root rules are one file per rule under
|
|
50447
|
+
* `~/.devin/rules/*.md` with the same `trigger`/`globs` frontmatter as project
|
|
50448
|
+
* scope. Note the directory split — the per-rule global directory is the home
|
|
50449
|
+
* `~/.devin/`, not the `~/.config/devin/` tree every other Devin global
|
|
50450
|
+
* surface uses. That is what the rules page documents.
|
|
49632
50451
|
*
|
|
49633
50452
|
* Trigger inference (when no explicit devin trigger is persisted):
|
|
49634
50453
|
* - Specific globs (non wildcard) → glob
|
|
@@ -49659,19 +50478,32 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49659
50478
|
relativeFilePath: DEVIN_GLOBAL_AGENTS_FILE_NAME
|
|
49660
50479
|
};
|
|
49661
50480
|
}
|
|
50481
|
+
/**
|
|
50482
|
+
* The always-on root rule file for a scope: the project-root `AGENTS.md`
|
|
50483
|
+
* Devin CLI / Devin Local reads, or `~/.config/devin/AGENTS.md` in global
|
|
50484
|
+
* scope. Single-sourced so `getSettablePaths`, `fromFile` and
|
|
50485
|
+
* `fromRulesyncRule` cannot disagree about it.
|
|
50486
|
+
*/
|
|
50487
|
+
static getRootPath(global, excludeToolDir) {
|
|
50488
|
+
return global ? DevinRule.getGlobalRootPath(excludeToolDir) : {
|
|
50489
|
+
relativeDirPath: ".",
|
|
50490
|
+
relativeFilePath: "AGENTS.md"
|
|
50491
|
+
};
|
|
50492
|
+
}
|
|
49662
50493
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
49663
|
-
if (global) return {
|
|
50494
|
+
if (global) return {
|
|
50495
|
+
root: DevinRule.getRootPath(true, excludeToolDir),
|
|
50496
|
+
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
50497
|
+
};
|
|
49664
50498
|
return {
|
|
49665
|
-
root:
|
|
49666
|
-
relativeDirPath: ".",
|
|
49667
|
-
relativeFilePath: "AGENTS.md"
|
|
49668
|
-
},
|
|
50499
|
+
root: DevinRule.getRootPath(false, excludeToolDir),
|
|
49669
50500
|
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
49670
50501
|
};
|
|
49671
50502
|
}
|
|
49672
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
49673
|
-
|
|
49674
|
-
|
|
50503
|
+
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
50504
|
+
const nonRootDirPath = buildToolPath(DEVIN_DIR, "rules");
|
|
50505
|
+
if (relativeDirPath !== void 0 ? relativeDirPath !== nonRootDirPath : relativeFilePath === DevinRule.getRootPath(global).relativeFilePath) {
|
|
50506
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49675
50507
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootPath.relativeDirPath, rootPath.relativeFilePath));
|
|
49676
50508
|
return new DevinRule({
|
|
49677
50509
|
outputRoot,
|
|
@@ -49683,19 +50515,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49683
50515
|
root: true
|
|
49684
50516
|
});
|
|
49685
50517
|
}
|
|
49686
|
-
if (relativeFilePath === "AGENTS.md") {
|
|
49687
|
-
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeFilePath));
|
|
49688
|
-
return new DevinRule({
|
|
49689
|
-
outputRoot,
|
|
49690
|
-
relativeDirPath: ".",
|
|
49691
|
-
relativeFilePath: "AGENTS.md",
|
|
49692
|
-
frontmatter: {},
|
|
49693
|
-
body: fileContent,
|
|
49694
|
-
validate,
|
|
49695
|
-
root: true
|
|
49696
|
-
});
|
|
49697
|
-
}
|
|
49698
|
-
const nonRootDirPath = buildToolPath(DEVIN_DIR, "rules");
|
|
49699
50518
|
const filePath = (0, node_path.join)(outputRoot, nonRootDirPath, relativeFilePath);
|
|
49700
50519
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
49701
50520
|
let parsedFrontmatter;
|
|
@@ -49715,8 +50534,9 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49715
50534
|
});
|
|
49716
50535
|
}
|
|
49717
50536
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
49718
|
-
|
|
49719
|
-
|
|
50537
|
+
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
50538
|
+
if (rulesyncFrontmatter.root) {
|
|
50539
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49720
50540
|
return new DevinRule({
|
|
49721
50541
|
outputRoot,
|
|
49722
50542
|
relativeDirPath: rootPath.relativeDirPath,
|
|
@@ -49727,16 +50547,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49727
50547
|
root: true
|
|
49728
50548
|
});
|
|
49729
50549
|
}
|
|
49730
|
-
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
49731
|
-
if (rulesyncFrontmatter.root) return new DevinRule({
|
|
49732
|
-
outputRoot,
|
|
49733
|
-
relativeDirPath: ".",
|
|
49734
|
-
relativeFilePath: "AGENTS.md",
|
|
49735
|
-
frontmatter: {},
|
|
49736
|
-
body: rulesyncRule.getBody(),
|
|
49737
|
-
validate,
|
|
49738
|
-
root: true
|
|
49739
|
-
});
|
|
49740
50550
|
const storedDevin = rulesyncFrontmatter.devin;
|
|
49741
50551
|
const normalized = normalizeStoredDevin(storedDevin);
|
|
49742
50552
|
const storedTrigger = storedDevin?.trigger;
|
|
@@ -49796,7 +50606,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49796
50606
|
error: null
|
|
49797
50607
|
};
|
|
49798
50608
|
}
|
|
49799
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath
|
|
50609
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
49800
50610
|
return new DevinRule({
|
|
49801
50611
|
outputRoot,
|
|
49802
50612
|
relativeDirPath,
|
|
@@ -49804,7 +50614,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49804
50614
|
frontmatter: {},
|
|
49805
50615
|
body: "",
|
|
49806
50616
|
validate: false,
|
|
49807
|
-
root:
|
|
50617
|
+
root: relativeDirPath !== buildToolPath(DEVIN_DIR, "rules")
|
|
49808
50618
|
});
|
|
49809
50619
|
}
|
|
49810
50620
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -50437,6 +51247,11 @@ var KimiCodeRule = class KimiCodeRule extends ToolRule {
|
|
|
50437
51247
|
};
|
|
50438
51248
|
//#endregion
|
|
50439
51249
|
//#region src/features/rules/kiro-rule.ts
|
|
51250
|
+
/**
|
|
51251
|
+
* Kiro loads `AGENTS.md` as steering context from any directory in the
|
|
51252
|
+
* workspace tree (CLI 2.18.0 / IDE 1.0.309), not just the workspace root.
|
|
51253
|
+
*/
|
|
51254
|
+
const KIRO_NESTED_STEERING_FILE_NAME = "AGENTS.md";
|
|
50440
51255
|
const WILDCARD_GLOBS = /* @__PURE__ */ new Set([
|
|
50441
51256
|
"**/*",
|
|
50442
51257
|
"**",
|
|
@@ -50510,8 +51325,17 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50510
51325
|
};
|
|
50511
51326
|
return { nonRoot: { relativeDirPath: steeringDirPath } };
|
|
50512
51327
|
}
|
|
50513
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
51328
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
50514
51329
|
const paths = this.getSettablePaths({ global });
|
|
51330
|
+
const steeringDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
51331
|
+
if (overrideDirPath !== void 0 && overrideDirPath !== steeringDirPath && overrideDirPath !== "." && relativeFilePath === KIRO_NESTED_STEERING_FILE_NAME) return new KiroRule({
|
|
51332
|
+
outputRoot,
|
|
51333
|
+
relativeDirPath: overrideDirPath,
|
|
51334
|
+
relativeFilePath,
|
|
51335
|
+
fileContent: await readFileContent((0, node_path.join)(outputRoot, overrideDirPath, relativeFilePath)),
|
|
51336
|
+
validate,
|
|
51337
|
+
root: false
|
|
51338
|
+
});
|
|
50515
51339
|
const isRoot = "root" in paths && relativeFilePath === paths.root.relativeFilePath;
|
|
50516
51340
|
const relativeDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
50517
51341
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
@@ -50535,6 +51359,12 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50535
51359
|
});
|
|
50536
51360
|
if (params.root) return new KiroRule(params);
|
|
50537
51361
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
51362
|
+
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
51363
|
+
if (!global && subprojectPath) return new KiroRule({
|
|
51364
|
+
...params,
|
|
51365
|
+
relativeDirPath: (0, node_path.join)(subprojectPath),
|
|
51366
|
+
relativeFilePath: KIRO_NESTED_STEERING_FILE_NAME
|
|
51367
|
+
});
|
|
50538
51368
|
const inclusion = deriveKiroInclusion({
|
|
50539
51369
|
kiro: frontmatter.kiro,
|
|
50540
51370
|
globs: frontmatter.globs
|
|
@@ -50545,7 +51375,34 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50545
51375
|
fileContent: stringifyFrontmatter(rulesyncRule.getBody(), inclusion)
|
|
50546
51376
|
});
|
|
50547
51377
|
}
|
|
51378
|
+
/**
|
|
51379
|
+
* The subproject directory a nested `AGENTS.md` scopes, or `undefined` for the
|
|
51380
|
+
* root rule and for the `.kiro/steering/` files.
|
|
51381
|
+
*/
|
|
51382
|
+
getSubprojectPath() {
|
|
51383
|
+
if (this.isRoot() || this.getRelativeFilePath() !== KIRO_NESTED_STEERING_FILE_NAME) return;
|
|
51384
|
+
const relativeDirPath = toPosixPath(this.getRelativeDirPath());
|
|
51385
|
+
if (relativeDirPath === "." || relativeDirPath === "" || relativeDirPath.startsWith(".")) return;
|
|
51386
|
+
return relativeDirPath;
|
|
51387
|
+
}
|
|
50548
51388
|
toRulesyncRule() {
|
|
51389
|
+
const subprojectPath = this.getSubprojectPath();
|
|
51390
|
+
if (subprojectPath !== void 0) {
|
|
51391
|
+
const slug = subprojectPath.replaceAll("/", "-");
|
|
51392
|
+
return new RulesyncRule({
|
|
51393
|
+
outputRoot: this.getOutputRoot(),
|
|
51394
|
+
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
51395
|
+
relativeFilePath: `${slug}-kiro.md`,
|
|
51396
|
+
frontmatter: {
|
|
51397
|
+
targets: ["kiro"],
|
|
51398
|
+
root: false,
|
|
51399
|
+
globs: [`${subprojectPath}/**/*`],
|
|
51400
|
+
agentsmd: { subprojectPath }
|
|
51401
|
+
},
|
|
51402
|
+
body: this.getFileContent(),
|
|
51403
|
+
validate: true
|
|
51404
|
+
});
|
|
51405
|
+
}
|
|
50549
51406
|
const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getRelativeFilePath());
|
|
50550
51407
|
const inclusion = typeof frontmatter.inclusion === "string" ? frontmatter.inclusion : void 0;
|
|
50551
51408
|
if (!inclusion) return this.toRulesyncRuleDefault();
|
|
@@ -50578,6 +51435,26 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50578
51435
|
error: null
|
|
50579
51436
|
};
|
|
50580
51437
|
}
|
|
51438
|
+
/**
|
|
51439
|
+
* Nested `AGENTS.md` files anywhere in the workspace tree, which Kiro loads as
|
|
51440
|
+
* steering context alongside `.kiro/steering/`. Import-only, like every other
|
|
51441
|
+
* nested scan: these are hand-authored files outside a rulesync-owned
|
|
51442
|
+
* directory, so enumerating them for `--delete` would sweep away work rulesync
|
|
51443
|
+
* never wrote.
|
|
51444
|
+
* @see https://kiro.dev/docs/steering/
|
|
51445
|
+
*/
|
|
51446
|
+
static getNestedFilePatterns({ outputRoot }) {
|
|
51447
|
+
const root = toPosixPath(outputRoot);
|
|
51448
|
+
return {
|
|
51449
|
+
include: [`${root}/**/${KIRO_NESTED_STEERING_FILE_NAME}`],
|
|
51450
|
+
ignore: [
|
|
51451
|
+
`${root}/${KIRO_NESTED_STEERING_FILE_NAME}`,
|
|
51452
|
+
`${root}/**/.*/**`,
|
|
51453
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
51454
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
51455
|
+
]
|
|
51456
|
+
};
|
|
51457
|
+
}
|
|
50581
51458
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
50582
51459
|
return new KiroRule({
|
|
50583
51460
|
outputRoot,
|
|
@@ -51428,11 +52305,12 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
51428
52305
|
static getSettablePaths(_options = {}) {
|
|
51429
52306
|
return { nonRoot: { relativeDirPath: buildToolPath(ROO_DIR, "rules", _options.excludeToolDir) } };
|
|
51430
52307
|
}
|
|
51431
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
51432
|
-
const
|
|
52308
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true }) {
|
|
52309
|
+
const relativeDirPath = overrideDirPath !== void 0 && RooRule.extractModeFromDirPath(overrideDirPath) !== void 0 ? overrideDirPath : this.getSettablePaths().nonRoot.relativeDirPath;
|
|
52310
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
51433
52311
|
return new RooRule({
|
|
51434
52312
|
outputRoot,
|
|
51435
|
-
relativeDirPath
|
|
52313
|
+
relativeDirPath,
|
|
51436
52314
|
relativeFilePath,
|
|
51437
52315
|
fileContent,
|
|
51438
52316
|
validate,
|
|
@@ -51440,12 +52318,16 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
51440
52318
|
});
|
|
51441
52319
|
}
|
|
51442
52320
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
51443
|
-
|
|
52321
|
+
const params = this.buildToolRuleParamsDefault({
|
|
51444
52322
|
outputRoot,
|
|
51445
52323
|
rulesyncRule,
|
|
51446
52324
|
validate,
|
|
51447
52325
|
nonRootPath: this.getSettablePaths().nonRoot
|
|
51448
|
-
})
|
|
52326
|
+
});
|
|
52327
|
+
const mode = rulesyncRule.getFrontmatter().roo?.mode;
|
|
52328
|
+
if (!params.root && mode !== void 0 && mode !== "") if (!ROO_MODE_SLUG_PATTERN.test(mode)) warnWithFallback(void 0, `Ignoring roo.mode "${mode}" on ${rulesyncRule.getRelativeFilePath()}: a mode slug may contain only letters, digits and hyphens. Writing the rule to ${params.relativeDirPath} instead.`);
|
|
52329
|
+
else params.relativeDirPath = (0, node_path.join)((0, node_path.dirname)(params.relativeDirPath), rooModeRulesDirName(mode));
|
|
52330
|
+
return new RooRule(params);
|
|
51449
52331
|
}
|
|
51450
52332
|
/**
|
|
51451
52333
|
* Extract mode slug from file path for mode-specific rules
|
|
@@ -51457,8 +52339,51 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
51457
52339
|
const singleFileMatch = filePath.match(/\.(roo|cline)rules-([a-zA-Z0-9-]+)$/);
|
|
51458
52340
|
if (singleFileMatch) return singleFileMatch[2];
|
|
51459
52341
|
}
|
|
52342
|
+
/**
|
|
52343
|
+
* The mode slug of a `.roo/rules-{mode}/` directory, or `undefined` for the
|
|
52344
|
+
* generic `.roo/rules/` directory and anything else. Path-shaped input is
|
|
52345
|
+
* rejected by the slug pattern, so a crafted directory name cannot be lifted
|
|
52346
|
+
* back into frontmatter.
|
|
52347
|
+
*/
|
|
52348
|
+
static extractModeFromDirPath(relativeDirPath) {
|
|
52349
|
+
for (const segment of toPosixPath(relativeDirPath).split("/")) {
|
|
52350
|
+
if (!segment.startsWith("rules-")) continue;
|
|
52351
|
+
const mode = segment.slice(6);
|
|
52352
|
+
if (ROO_MODE_SLUG_PATTERN.test(mode)) return mode;
|
|
52353
|
+
}
|
|
52354
|
+
}
|
|
51460
52355
|
toRulesyncRule() {
|
|
51461
|
-
|
|
52356
|
+
const mode = RooRule.extractModeFromDirPath(this.getRelativeDirPath());
|
|
52357
|
+
if (mode === void 0) return this.toRulesyncRuleDefault();
|
|
52358
|
+
const baseName = this.getRelativeFilePath().replace(/\.md$/, "");
|
|
52359
|
+
const suffix = `-${mode}`;
|
|
52360
|
+
const importedName = baseName.endsWith(suffix) ? baseName : `${baseName}${suffix}`;
|
|
52361
|
+
return new RulesyncRule({
|
|
52362
|
+
outputRoot: this.getOutputRoot(),
|
|
52363
|
+
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
52364
|
+
relativeFilePath: `${importedName}.md`,
|
|
52365
|
+
frontmatter: {
|
|
52366
|
+
root: false,
|
|
52367
|
+
targets: [this.constructor.getToolTargetName()],
|
|
52368
|
+
description: this.description,
|
|
52369
|
+
globs: this.globs ?? [],
|
|
52370
|
+
roo: { mode }
|
|
52371
|
+
},
|
|
52372
|
+
body: this.getFileContent(),
|
|
52373
|
+
validate: true
|
|
52374
|
+
});
|
|
52375
|
+
}
|
|
52376
|
+
/**
|
|
52377
|
+
* Mode-specific rule directories (`.roo/rules-{mode}/`), which Roo/Zoo Code
|
|
52378
|
+
* load instead of `.roo/rules/` while that mode is active. Import-only: the
|
|
52379
|
+
* generic directory is the only one the deletion sweep enumerates, because a
|
|
52380
|
+
* `rules-*` glob would also match mode rules a user wrote by hand.
|
|
52381
|
+
*/
|
|
52382
|
+
static getNestedFilePatterns({ outputRoot }) {
|
|
52383
|
+
return {
|
|
52384
|
+
include: [`${toPosixPath(outputRoot)}/${toPosixPath(ROO_DIR)}/rules-*/**/*.md`],
|
|
52385
|
+
ignore: []
|
|
52386
|
+
};
|
|
51462
52387
|
}
|
|
51463
52388
|
validate() {
|
|
51464
52389
|
return {
|
|
@@ -51479,10 +52404,18 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
51479
52404
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
51480
52405
|
return this.isTargetedByRulesyncRuleDefault({
|
|
51481
52406
|
rulesyncRule,
|
|
51482
|
-
toolTarget:
|
|
52407
|
+
toolTarget: this.getToolTargetName()
|
|
51483
52408
|
});
|
|
51484
52409
|
}
|
|
51485
52410
|
/**
|
|
52411
|
+
* The tool target this class imports as. `ZoocodeRule` narrows the same
|
|
52412
|
+
* `.roo/` adapters to the `zoocode` target, so the name has to come from the
|
|
52413
|
+
* class rather than a literal.
|
|
52414
|
+
*/
|
|
52415
|
+
static getToolTargetName() {
|
|
52416
|
+
return "roo";
|
|
52417
|
+
}
|
|
52418
|
+
/**
|
|
51486
52419
|
* Glob for the `separate-local-file` deletion; Roo reads `AGENTS.local.md`
|
|
51487
52420
|
* at the project root, not under `.roo/` (mirrors rovodev).
|
|
51488
52421
|
*/
|
|
@@ -52015,11 +52948,8 @@ var ZedRule = class ZedRule extends ToolRule {
|
|
|
52015
52948
|
* @see https://docs.zoocode.dev
|
|
52016
52949
|
*/
|
|
52017
52950
|
var ZoocodeRule = class extends RooRule {
|
|
52018
|
-
static
|
|
52019
|
-
return
|
|
52020
|
-
rulesyncRule,
|
|
52021
|
-
toolTarget: "zoocode"
|
|
52022
|
-
});
|
|
52951
|
+
static getToolTargetName() {
|
|
52952
|
+
return "zoocode";
|
|
52023
52953
|
}
|
|
52024
52954
|
};
|
|
52025
52955
|
//#endregion
|
|
@@ -52046,6 +52976,18 @@ const resolveIncludeLocalRoot = (options) => {
|
|
|
52046
52976
|
return parsed.data.includeLocalRoot ?? true;
|
|
52047
52977
|
};
|
|
52048
52978
|
/**
|
|
52979
|
+
* Whether `candidate` is `base` itself or a class that extends it.
|
|
52980
|
+
*
|
|
52981
|
+
* Class-identity dispatch (`factory.class === Base`) is subclass-blind: a target
|
|
52982
|
+
* whose adapter merely narrows another one — `ZoocodeRule extends RooRule` — fails
|
|
52983
|
+
* a strict `===` and silently falls through. That is how `zoocode` ended up
|
|
52984
|
+
* declaring `localRootMode: "separate-local-file"` while never emitting
|
|
52985
|
+
* `AGENTS.local.md`, in both directions and without a warning.
|
|
52986
|
+
*/
|
|
52987
|
+
function isClassOrSubclassOf({ candidate, base }) {
|
|
52988
|
+
return candidate === base || Object.prototype.isPrototypeOf.call(base, candidate);
|
|
52989
|
+
}
|
|
52990
|
+
/**
|
|
52049
52991
|
* Factory Map mapping tool targets to their rule factories.
|
|
52050
52992
|
* Using Map to preserve insertion order for consistent iteration.
|
|
52051
52993
|
*/
|
|
@@ -52399,7 +53341,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
52399
53341
|
meta: {
|
|
52400
53342
|
extension: "md",
|
|
52401
53343
|
supportsGlobal: true,
|
|
52402
|
-
ruleDiscoveryMode: "auto"
|
|
53344
|
+
ruleDiscoveryMode: "auto",
|
|
53345
|
+
localRootMode: "separate-local-file",
|
|
53346
|
+
localRootFileName: "AGENTS.local.md"
|
|
52403
53347
|
}
|
|
52404
53348
|
}],
|
|
52405
53349
|
["zed", {
|
|
@@ -52686,7 +53630,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52686
53630
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
52687
53631
|
}
|
|
52688
53632
|
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
52689
|
-
if (
|
|
53633
|
+
if (isClassOrSubclassOf({
|
|
53634
|
+
candidate: factory.class,
|
|
53635
|
+
base: ClaudecodeRule
|
|
53636
|
+
})) {
|
|
52690
53637
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
52691
53638
|
return new ClaudecodeRule({
|
|
52692
53639
|
outputRoot: this.outputRoot,
|
|
@@ -52699,7 +53646,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52699
53646
|
localRoot
|
|
52700
53647
|
});
|
|
52701
53648
|
}
|
|
52702
|
-
if (
|
|
53649
|
+
if (isClassOrSubclassOf({
|
|
53650
|
+
candidate: factory.class,
|
|
53651
|
+
base: ClaudecodeLegacyRule
|
|
53652
|
+
})) {
|
|
52703
53653
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
52704
53654
|
return new ClaudecodeLegacyRule({
|
|
52705
53655
|
outputRoot: this.outputRoot,
|
|
@@ -52711,7 +53661,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52711
53661
|
localRoot
|
|
52712
53662
|
});
|
|
52713
53663
|
}
|
|
52714
|
-
if (
|
|
53664
|
+
if (isClassOrSubclassOf({
|
|
53665
|
+
candidate: factory.class,
|
|
53666
|
+
base: RovodevRule
|
|
53667
|
+
})) return new RovodevRule({
|
|
52715
53668
|
outputRoot: this.outputRoot,
|
|
52716
53669
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52717
53670
|
relativeFilePath: fileName,
|
|
@@ -52720,7 +53673,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52720
53673
|
root: true,
|
|
52721
53674
|
localRoot
|
|
52722
53675
|
});
|
|
52723
|
-
if (
|
|
53676
|
+
if (isClassOrSubclassOf({
|
|
53677
|
+
candidate: factory.class,
|
|
53678
|
+
base: RooRule
|
|
53679
|
+
})) return new RooRule({
|
|
52724
53680
|
outputRoot: this.outputRoot,
|
|
52725
53681
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52726
53682
|
relativeFilePath: fileName,
|
|
@@ -52729,7 +53685,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52729
53685
|
root: true,
|
|
52730
53686
|
localRoot
|
|
52731
53687
|
});
|
|
52732
|
-
if (
|
|
53688
|
+
if (isClassOrSubclassOf({
|
|
53689
|
+
candidate: factory.class,
|
|
53690
|
+
base: DevinRule
|
|
53691
|
+
})) return new DevinRule({
|
|
53692
|
+
outputRoot: this.outputRoot,
|
|
53693
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
53694
|
+
relativeFilePath: fileName,
|
|
53695
|
+
frontmatter: {},
|
|
53696
|
+
body,
|
|
53697
|
+
validate: true,
|
|
53698
|
+
root: true,
|
|
53699
|
+
localRoot
|
|
53700
|
+
});
|
|
53701
|
+
if (isClassOrSubclassOf({
|
|
53702
|
+
candidate: factory.class,
|
|
53703
|
+
base: QwencodeRule
|
|
53704
|
+
})) return new QwencodeRule({
|
|
52733
53705
|
outputRoot: this.outputRoot,
|
|
52734
53706
|
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
52735
53707
|
relativeFilePath: fileName,
|