rulesync 16.12.0 → 16.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.cjs +9 -4
- package/dist/cli/index.js +9 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BwdPcfzS.cjs → import-ChnrtxiJ.cjs} +840 -150
- package/dist/{import-zGCKgpdt.js → import-Dswkc7Ub.js} +826 -136
- package/dist/import-Dswkc7Ub.js.map +1 -0
- 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$289 } = node_path.posix;
|
|
138
138
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
139
139
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
140
140
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
141
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
142
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
143
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
144
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
145
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
146
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
147
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
148
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
149
|
-
join$
|
|
150
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
151
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
141
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
142
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$289(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
143
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
144
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
145
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
146
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
147
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
148
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
149
|
+
join$289(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
150
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
151
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
152
152
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
153
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
153
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
154
154
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
155
155
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
156
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
157
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
156
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$289(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
157
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$289(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
158
158
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
159
159
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
160
160
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -437,6 +437,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
437
437
|
"kiro-cli",
|
|
438
438
|
"kiro-ide",
|
|
439
439
|
"opencode",
|
|
440
|
+
"pi",
|
|
440
441
|
"qwencode",
|
|
441
442
|
"reasonix",
|
|
442
443
|
"rovodev",
|
|
@@ -1928,7 +1929,7 @@ function isPlainObject$1(value) {
|
|
|
1928
1929
|
/**
|
|
1929
1930
|
* Type guard to check if a value is an array of strings.
|
|
1930
1931
|
*/
|
|
1931
|
-
function isStringArray$
|
|
1932
|
+
function isStringArray$2(value) {
|
|
1932
1933
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1933
1934
|
}
|
|
1934
1935
|
//#endregion
|
|
@@ -2296,6 +2297,7 @@ const HOOK_EVENTS = [
|
|
|
2296
2297
|
"todoCreated",
|
|
2297
2298
|
"todoCompleted",
|
|
2298
2299
|
"stopFailure",
|
|
2300
|
+
"stopCancelled",
|
|
2299
2301
|
"instructionsLoaded",
|
|
2300
2302
|
"userPromptExpansion",
|
|
2301
2303
|
"postToolBatch",
|
|
@@ -2871,6 +2873,7 @@ const GROKCLI_HOOK_EVENTS = [
|
|
|
2871
2873
|
"permissionDenied",
|
|
2872
2874
|
"stop",
|
|
2873
2875
|
"stopFailure",
|
|
2876
|
+
"stopCancelled",
|
|
2874
2877
|
"notification",
|
|
2875
2878
|
"subagentStart",
|
|
2876
2879
|
"subagentStop",
|
|
@@ -2946,16 +2949,10 @@ const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CAN
|
|
|
2946
2949
|
/**
|
|
2947
2950
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
2948
2951
|
*
|
|
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`.
|
|
2952
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
2953
|
+
* of v0.20.2 (`v2026.8.16`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
2954
|
+
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
2955
|
+
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
2959
2956
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
2960
2957
|
*/
|
|
2961
2958
|
const HERMESAGENT_HOOK_EVENTS = [
|
|
@@ -2968,6 +2965,21 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
2968
2965
|
"subagentStart",
|
|
2969
2966
|
"subagentStop"
|
|
2970
2967
|
];
|
|
2968
|
+
/**
|
|
2969
|
+
* Hermes Agent's native `VALID_HOOKS` set, in upstream source order.
|
|
2970
|
+
*
|
|
2971
|
+
* The authority is `hermes_cli/plugins.py`'s `VALID_HOOKS`, not the docs: at
|
|
2972
|
+
* v0.20.2 `website/docs/user-guide/features/plugins.md` says "26 lifecycle
|
|
2973
|
+
* events" and then lists 30, omitting the five `on_kanban_*` worker/task/dispatch
|
|
2974
|
+
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
2975
|
+
* Shell hooks are gated on the same set.
|
|
2976
|
+
*
|
|
2977
|
+
* Verified against the tag `v2026.8.16` (v0.20.2): `VALID_HOOKS` holds 37
|
|
2978
|
+
* entries, grown from 23 at `v2026.8.3` (v0.20.0). This list holds 36 of them —
|
|
2979
|
+
* see {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
2980
|
+
*
|
|
2981
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py
|
|
2982
|
+
*/
|
|
2971
2983
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
2972
2984
|
"pre_tool_call",
|
|
2973
2985
|
"post_tool_call",
|
|
@@ -2976,6 +2988,10 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2976
2988
|
"transform_llm_output",
|
|
2977
2989
|
"pre_llm_call",
|
|
2978
2990
|
"post_llm_call",
|
|
2991
|
+
"on_stream_start",
|
|
2992
|
+
"on_stream_delta",
|
|
2993
|
+
"on_stream_end",
|
|
2994
|
+
"on_interim_message",
|
|
2979
2995
|
"pre_verify",
|
|
2980
2996
|
"pre_api_request",
|
|
2981
2997
|
"post_api_request",
|
|
@@ -2984,16 +3000,39 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
2984
3000
|
"on_session_end",
|
|
2985
3001
|
"on_session_finalize",
|
|
2986
3002
|
"on_session_reset",
|
|
3003
|
+
"on_skill_lifecycle",
|
|
2987
3004
|
"subagent_start",
|
|
2988
3005
|
"subagent_stop",
|
|
2989
3006
|
"pre_gateway_dispatch",
|
|
2990
3007
|
"pre_approval_request",
|
|
2991
3008
|
"post_approval_response",
|
|
3009
|
+
"pre_transcription",
|
|
2992
3010
|
"kanban_task_claimed",
|
|
2993
3011
|
"kanban_task_completed",
|
|
2994
|
-
"kanban_task_blocked"
|
|
3012
|
+
"kanban_task_blocked",
|
|
3013
|
+
"on_kanban_worker_spawned",
|
|
3014
|
+
"on_kanban_worker_exited",
|
|
3015
|
+
"on_kanban_worker_stale_claim",
|
|
3016
|
+
"on_kanban_task_updated",
|
|
3017
|
+
"on_kanban_dispatch_tick",
|
|
3018
|
+
"gateway_platform_event",
|
|
3019
|
+
"pre_command"
|
|
2995
3020
|
];
|
|
2996
3021
|
/**
|
|
3022
|
+
* `VALID_HOOKS` entries a SHELL hook may not register for. `VALID_HOOKS` doubles
|
|
3023
|
+
* as the shell-hook allow-list, but `SHELL_UNSUPPORTED_HOOKS` is subtracted from
|
|
3024
|
+
* it first: shell hooks cannot return these events' directives, so
|
|
3025
|
+
* `_parse_hooks_block` refuses the registration with a warning rather than
|
|
3026
|
+
* accepting it and dropping the return value.
|
|
3027
|
+
*
|
|
3028
|
+
* rulesync only ever writes shell hooks, so these are excluded from
|
|
3029
|
+
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
3030
|
+
*
|
|
3031
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
3032
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.8.16/agent/shell_hooks.py — `_parse_hooks_block`
|
|
3033
|
+
*/
|
|
3034
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
3035
|
+
/**
|
|
2997
3036
|
* Map canonical camelCase event names to Hermes Agent's native `VALID_HOOKS`
|
|
2998
3037
|
* snake_case keys under the `hooks:` block of `~/.hermes/config.yaml`.
|
|
2999
3038
|
*/
|
|
@@ -3610,6 +3649,7 @@ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
|
3610
3649
|
permissionDenied: "PermissionDenied",
|
|
3611
3650
|
stop: "Stop",
|
|
3612
3651
|
stopFailure: "StopFailure",
|
|
3652
|
+
stopCancelled: "StopCancelled",
|
|
3613
3653
|
notification: "Notification",
|
|
3614
3654
|
subagentStart: "SubagentStart",
|
|
3615
3655
|
subagentStop: "SubagentStop",
|
|
@@ -3884,6 +3924,25 @@ const McpServerSchema = zod_mini.z.looseObject({
|
|
|
3884
3924
|
kiroAutoApprove: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3885
3925
|
kiroAutoBlock: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3886
3926
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
3927
|
+
/**
|
|
3928
|
+
* The canonical per-server tool allowlist.
|
|
3929
|
+
*
|
|
3930
|
+
* **Collision rule for adapters.** Several tools spell an allowlist natively
|
|
3931
|
+
* under a name that also exists here, so a single server entry can carry both
|
|
3932
|
+
* a canonical field and its native counterpart. What the *native* key means
|
|
3933
|
+
* decides the resolution — not which one was written first:
|
|
3934
|
+
*
|
|
3935
|
+
* - Native key is a redundant spelling of the same concept, and both lists are
|
|
3936
|
+
* additive → **merge** them, so a config that already spells the field
|
|
3937
|
+
* natively keeps working (`kiroAutoApprove`/`kiroAutoBlock` in
|
|
3938
|
+
* `kiro-mcp.ts`).
|
|
3939
|
+
* - Native key is the same concept but rulesync owns the generated value, so
|
|
3940
|
+
* keeping both would be ambiguous → the **canonical value wins** and the
|
|
3941
|
+
* native one is dropped with a warning (`tools` in `copilotcli-mcp.ts`).
|
|
3942
|
+
* - Native key of the same name means something else entirely → **refuse the
|
|
3943
|
+
* canonical value** rather than write a shape the tool misreads (`tools` in
|
|
3944
|
+
* `codexcli-mcp.ts`, where Codex reads it as a per-tool approval table).
|
|
3945
|
+
*/
|
|
3887
3946
|
enabledTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
3888
3947
|
disabledTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
3889
3948
|
});
|
|
@@ -4726,8 +4785,65 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4726
4785
|
dangerouslyAllowAll: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4727
4786
|
});
|
|
4728
4787
|
/**
|
|
4788
|
+
* Tool-scoped override block for Pi Coding Agent. Pi has no allow/ask/deny rule
|
|
4789
|
+
* surface: the only repository-syncable tool gate is `defaultTools`, the list of
|
|
4790
|
+
* built-in tools enabled at startup (added in v0.84.2). It is an enable-list
|
|
4791
|
+
* rather than a canonical allow/deny rule set, so it is authored here rather
|
|
4792
|
+
* than mapped onto `permission`, mirroring `KimiCodePermissionsOverrideSchema`'s
|
|
4793
|
+
* `tools.enabled` and `VibePermissionsOverrideSchema`'s `enabled_tools`.
|
|
4794
|
+
*
|
|
4795
|
+
* An empty array is meaningful upstream — it starts Pi with no built-in tools
|
|
4796
|
+
* while keeping extension and SDK custom tools — so it is emitted as written.
|
|
4797
|
+
* CLI flags (`--tools`, `--no-tools`, `--no-builtin-tools`, `--exclude-tools`)
|
|
4798
|
+
* outrank the setting, and a project array **replaces** the global one rather
|
|
4799
|
+
* than merging with it.
|
|
4800
|
+
*
|
|
4801
|
+
* @example
|
|
4802
|
+
* { "defaultTools": ["bash", "edit", "write"] }
|
|
4803
|
+
* @see https://pi.dev/docs/latest/settings
|
|
4804
|
+
*/
|
|
4805
|
+
const PiPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4806
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4807
|
+
defaultTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4808
|
+
});
|
|
4809
|
+
/**
|
|
4810
|
+
* The Antigravity CLI's four documented `toolPermission` autonomy presets.
|
|
4811
|
+
* Exported so the import side of `antigravity-cli-permissions.ts` filters
|
|
4812
|
+
* against exactly the values the enum below accepts — an unrecognized value
|
|
4813
|
+
* carried in verbatim would fail validation of the *whole*
|
|
4814
|
+
* `.rulesync/permissions.jsonc`, not just that key.
|
|
4815
|
+
* @see https://antigravity.google/docs/cli/reference
|
|
4816
|
+
*/
|
|
4817
|
+
const ANTIGRAVITY_CLI_TOOL_PERMISSIONS = [
|
|
4818
|
+
"request-review",
|
|
4819
|
+
"proceed-in-sandbox",
|
|
4820
|
+
"always-proceed",
|
|
4821
|
+
"strict"
|
|
4822
|
+
];
|
|
4823
|
+
/**
|
|
4824
|
+
* The Antigravity CLI's three documented `artifactReviewPolicy` values. Shared
|
|
4825
|
+
* with the import filter for the same reason as
|
|
4826
|
+
* {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4827
|
+
* @see https://antigravity.google/docs/cli/settings
|
|
4828
|
+
*/
|
|
4829
|
+
const ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES = [
|
|
4830
|
+
"asks-for-review",
|
|
4831
|
+
"agent-decides",
|
|
4832
|
+
"always-proceed"
|
|
4833
|
+
];
|
|
4834
|
+
/**
|
|
4835
|
+
* The baseline execution modes the CLI persists as `agentMode`. Shared with the
|
|
4836
|
+
* import filter for the same reason as {@link ANTIGRAVITY_CLI_TOOL_PERMISSIONS}.
|
|
4837
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4838
|
+
*/
|
|
4839
|
+
const ANTIGRAVITY_CLI_AGENT_MODES = [
|
|
4840
|
+
"default",
|
|
4841
|
+
"accept-edits",
|
|
4842
|
+
"plan"
|
|
4843
|
+
];
|
|
4844
|
+
/**
|
|
4729
4845
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4730
|
-
* `settings.json` carries
|
|
4846
|
+
* `settings.json` carries five global autonomy/sandbox knobs outside the
|
|
4731
4847
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4732
4848
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4733
4849
|
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
@@ -4735,7 +4851,8 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4735
4851
|
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4736
4852
|
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4737
4853
|
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4738
|
-
* active workspace roots)
|
|
4854
|
+
* active workspace roots), and `agentMode` (the baseline execution mode a session
|
|
4855
|
+
* starts in — `default` / `accept-edits` / `plan`). Antigravity applies the allow/deny
|
|
4739
4856
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4740
4857
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4741
4858
|
* Fields placed here are merged onto the top level of
|
|
@@ -4743,27 +4860,27 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4743
4860
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4744
4861
|
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
4745
4862
|
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4746
|
-
* https://antigravity.google/docs/cli/sandbox
|
|
4747
|
-
* https://antigravity.google/docs/cli/settings
|
|
4863
|
+
* https://antigravity.google/docs/cli/sandbox,
|
|
4864
|
+
* https://antigravity.google/docs/cli/settings and
|
|
4865
|
+
* https://antigravity.google/docs/cli/modes.
|
|
4748
4866
|
*
|
|
4749
4867
|
* @example
|
|
4750
4868
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
4751
4869
|
*/
|
|
4752
4870
|
const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4753
4871
|
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
|
-
])),
|
|
4872
|
+
toolPermission: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_TOOL_PERMISSIONS)),
|
|
4760
4873
|
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4761
|
-
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum(
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4874
|
+
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES)),
|
|
4875
|
+
allowNonWorkspaceAccess: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4876
|
+
/**
|
|
4877
|
+
* The baseline execution mode the CLI starts a session in (Agent Mode), the
|
|
4878
|
+
* persisted form of the `/settings` panel's Agent Mode row. Only
|
|
4879
|
+
* `~/.gemini/antigravity-cli/settings.json` is read for it, which is why this
|
|
4880
|
+
* override stays global-only.
|
|
4881
|
+
* @see https://antigravity.google/docs/cli/modes
|
|
4882
|
+
*/
|
|
4883
|
+
agentMode: zod_mini.z.optional(zod_mini.z.enum(ANTIGRAVITY_CLI_AGENT_MODES))
|
|
4767
4884
|
});
|
|
4768
4885
|
/**
|
|
4769
4886
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -5073,6 +5190,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
5073
5190
|
takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
|
|
5074
5191
|
amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
|
|
5075
5192
|
"antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema),
|
|
5193
|
+
pi: zod_mini.z.optional(PiPermissionsOverrideSchema),
|
|
5076
5194
|
augmentcode: zod_mini.z.optional(AugmentcodePermissionsOverrideSchema),
|
|
5077
5195
|
kiro: zod_mini.z.optional(KiroPermissionsOverrideSchema),
|
|
5078
5196
|
codexcli: zod_mini.z.optional(CodexcliPermissionsOverrideSchema),
|
|
@@ -5554,6 +5672,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
5554
5672
|
cursor: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5555
5673
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
5556
5674
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5675
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5557
5676
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
5558
5677
|
})),
|
|
5559
5678
|
factorydroid: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -5778,6 +5897,11 @@ async function getLocalSkillDirNames(outputRoot) {
|
|
|
5778
5897
|
* default with a per-target value. A defined section value (including `false`)
|
|
5779
5898
|
* always wins over the root default.
|
|
5780
5899
|
*
|
|
5900
|
+
* `devin` also consumes the root-level value — it maps `true` onto a user-only
|
|
5901
|
+
* `triggers` list (see `devin-skill.ts`) — but has no section key of the same
|
|
5902
|
+
* name, so it does not go through this helper. A `devin.triggers` section value
|
|
5903
|
+
* still overrides it.
|
|
5904
|
+
*
|
|
5781
5905
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5782
5906
|
*/
|
|
5783
5907
|
function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
@@ -5788,10 +5912,15 @@ function resolveDisableModelInvocation({ rootFrontmatter, section }) {
|
|
|
5788
5912
|
*
|
|
5789
5913
|
* The rulesync skill frontmatter exposes a root-level `user-invocable` default
|
|
5790
5914
|
* 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
|
|
5915
|
+
* copilotcli, cursor, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
|
|
5792
5916
|
* per-target value. A defined section value (including `false`) always wins
|
|
5793
5917
|
* over the root default.
|
|
5794
5918
|
*
|
|
5919
|
+
* `devin` also consumes the root-level value — it maps `false` onto a
|
|
5920
|
+
* model-only `triggers` list (see `devin-skill.ts`) — but has no section key of
|
|
5921
|
+
* the same name, so it does not go through this helper. A `devin.triggers`
|
|
5922
|
+
* section value still overrides it.
|
|
5923
|
+
*
|
|
5795
5924
|
* @returns The resolved boolean, or `undefined` when neither value is set.
|
|
5796
5925
|
*/
|
|
5797
5926
|
function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
@@ -7641,6 +7770,22 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7641
7770
|
}
|
|
7642
7771
|
}
|
|
7643
7772
|
},
|
|
7773
|
+
".pi/settings.json": {
|
|
7774
|
+
format: "json",
|
|
7775
|
+
invalidRootPolicy: "error",
|
|
7776
|
+
features: { permissions: {
|
|
7777
|
+
kind: "replace-owned-keys",
|
|
7778
|
+
ownedKeys: ["defaultTools"]
|
|
7779
|
+
} }
|
|
7780
|
+
},
|
|
7781
|
+
".pi/agent/settings.json": {
|
|
7782
|
+
format: "json",
|
|
7783
|
+
invalidRootPolicy: "error",
|
|
7784
|
+
features: { permissions: {
|
|
7785
|
+
kind: "replace-owned-keys",
|
|
7786
|
+
ownedKeys: ["defaultTools"]
|
|
7787
|
+
} }
|
|
7788
|
+
},
|
|
7644
7789
|
".github/copilot/settings.json": {
|
|
7645
7790
|
format: "json",
|
|
7646
7791
|
invalidRootPolicy: "error",
|
|
@@ -12227,6 +12372,8 @@ const PI_RULE_FILE_NAME = "AGENTS.md";
|
|
|
12227
12372
|
const PI_RULE_OVERRIDE_FILE_NAME = "AGENTS.override.md";
|
|
12228
12373
|
const PI_APPEND_SYSTEM_FILE_NAME = "APPEND_SYSTEM.md";
|
|
12229
12374
|
const PI_HOOKS_FILE_NAME = "rulesync-hooks.ts";
|
|
12375
|
+
const PI_SETTINGS_FILE_NAME = "settings.json";
|
|
12376
|
+
const PI_AGENT_DIR_PATH = PI_AGENT_DIR;
|
|
12230
12377
|
//#endregion
|
|
12231
12378
|
//#region src/features/commands/pi-command.ts
|
|
12232
12379
|
/**
|
|
@@ -14151,7 +14298,7 @@ const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
|
14151
14298
|
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
14152
14299
|
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
14153
14300
|
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
14154
|
-
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
14301
|
+
const isEmittableArray = ({ value }) => isStringArray$1(value);
|
|
14155
14302
|
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
14156
14303
|
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
14157
14304
|
/**
|
|
@@ -14460,7 +14607,7 @@ function isStringRecord(value) {
|
|
|
14460
14607
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
14461
14608
|
return Object.values(value).every((v) => typeof v === "string");
|
|
14462
14609
|
}
|
|
14463
|
-
function isStringArray(value) {
|
|
14610
|
+
function isStringArray$1(value) {
|
|
14464
14611
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
14465
14612
|
}
|
|
14466
14613
|
/** Compare object values without letting key order decide the answer. */
|
|
@@ -14492,7 +14639,7 @@ function isSafeStringRecord(value) {
|
|
|
14492
14639
|
* skipped wholesale when that read fails.
|
|
14493
14640
|
*/
|
|
14494
14641
|
function isSafeStringArray(value) {
|
|
14495
|
-
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14642
|
+
return isStringArray$1(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
14496
14643
|
}
|
|
14497
14644
|
/**
|
|
14498
14645
|
* A raw string kept only if the canonical field it would land in accepts it,
|
|
@@ -14534,7 +14681,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
14534
14681
|
return {
|
|
14535
14682
|
...url !== void 0 && { url },
|
|
14536
14683
|
...isStringRecord(headers) && { headers },
|
|
14537
|
-
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14684
|
+
...isStringArray$1(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
14538
14685
|
};
|
|
14539
14686
|
}
|
|
14540
14687
|
if (hookType === "mcp_tool") {
|
|
@@ -15517,7 +15664,7 @@ const MANAGED_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_CLINE_EVENT_NAMES
|
|
|
15517
15664
|
function parseManifest(fileContent) {
|
|
15518
15665
|
try {
|
|
15519
15666
|
const parsed = JSON.parse(fileContent);
|
|
15520
|
-
if (!isRecord$1(parsed) || !isStringArray$
|
|
15667
|
+
if (!isRecord$1(parsed) || !isStringArray$2(parsed.events)) return null;
|
|
15521
15668
|
return {
|
|
15522
15669
|
generatedBy: "rulesync",
|
|
15523
15670
|
events: parsed.events.filter((event) => MANAGED_EVENT_NAMES.has(event))
|
|
@@ -17152,6 +17299,7 @@ const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["pre_tool_call", "po
|
|
|
17152
17299
|
const HERMESAGENT_FAIL_CLOSED_EVENT = "pre_tool_call";
|
|
17153
17300
|
const HERMESAGENT_CANONICAL_EVENTS = new Set(HERMESAGENT_HOOK_EVENTS);
|
|
17154
17301
|
const HERMESAGENT_NATIVE_EVENTS = new Set(HERMESAGENT_NATIVE_HOOK_EVENTS);
|
|
17302
|
+
const HERMESAGENT_SHELL_UNSUPPORTED_EVENTS = new Set(HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS);
|
|
17155
17303
|
/**
|
|
17156
17304
|
* Whether an entry of the `hooks:` mapping is a hook-event list rather than one
|
|
17157
17305
|
* of its non-event siblings.
|
|
@@ -17240,7 +17388,8 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
17240
17388
|
}
|
|
17241
17389
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
17242
17390
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
17243
|
-
if (
|
|
17391
|
+
if (HERMESAGENT_SHELL_UNSUPPORTED_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is Python-plugin-only; Hermes Agent refuses it for shell hooks, so the generated entry will never run.`);
|
|
17392
|
+
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.20.2; preserving it for forward compatibility.`);
|
|
17244
17393
|
setHermesHookEntries({
|
|
17245
17394
|
result,
|
|
17246
17395
|
event: nativeEvent,
|
|
@@ -22364,7 +22513,7 @@ function isCodexStdioServer(config) {
|
|
|
22364
22513
|
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
22365
22514
|
*/
|
|
22366
22515
|
function isValidRenamedArray(key, value) {
|
|
22367
|
-
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$
|
|
22516
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$2(value);
|
|
22368
22517
|
}
|
|
22369
22518
|
/**
|
|
22370
22519
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
@@ -22884,6 +23033,49 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
22884
23033
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
22885
23034
|
};
|
|
22886
23035
|
/**
|
|
23036
|
+
* Copilot CLI's per-server tool allowlist. `tools` selects which of the
|
|
23037
|
+
* server's tools are exposed — `["*"]` (the default) for all, or a list of
|
|
23038
|
+
* names — so the canonical `enabledTools` is written under that name rather
|
|
23039
|
+
* than passed through as a key the CLI ignores.
|
|
23040
|
+
*
|
|
23041
|
+
* When a server carries both, the canonical `enabledTools` wins and the native
|
|
23042
|
+
* `tools` is dropped with a warning. rulesync owns the generated allowlist, and
|
|
23043
|
+
* the `tools` value most likely present is the upstream default `["*"]` — left
|
|
23044
|
+
* to win, it would silently discard a real allowlist in favour of "expose every
|
|
23045
|
+
* tool", which is the failure this mapping exists to prevent. That is the
|
|
23046
|
+
* "canonical wins" branch of the collision rule documented on `enabledTools` in
|
|
23047
|
+
* `src/types/mcp.ts`; `kiro-mcp.ts` merges instead (its native names are
|
|
23048
|
+
* redundant spellings of additive lists) and `codexcli-mcp.ts` refuses instead
|
|
23049
|
+
* (Codex's same-named key means a different thing).
|
|
23050
|
+
*
|
|
23051
|
+
* `disabledTools` has no counterpart upstream (expressing it would need the
|
|
23052
|
+
* server's full tool list), so the target keeps `supportsDisabledTools: false`
|
|
23053
|
+
* and the processor strips it before it reaches here.
|
|
23054
|
+
*
|
|
23055
|
+
* @see https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
|
|
23056
|
+
*/
|
|
23057
|
+
function renameEnabledToolsToTools({ server, serverName, logger }) {
|
|
23058
|
+
const { enabledTools, ...rest } = server;
|
|
23059
|
+
if (enabledTools === void 0) return rest;
|
|
23060
|
+
if (rest.tools !== void 0) logger?.warn(`[CopilotcliMcp] MCP server "${serverName}" declares both 'tools' and 'enabledTools'; keeping 'enabledTools' and dropping 'tools'.`);
|
|
23061
|
+
return {
|
|
23062
|
+
...rest,
|
|
23063
|
+
tools: enabledTools
|
|
23064
|
+
};
|
|
23065
|
+
}
|
|
23066
|
+
/**
|
|
23067
|
+
* Copilot CLI's `tools` accepts more than the canonical `enabledTools` shape:
|
|
23068
|
+
* `--tools` documents `*`, a comma-separated list, or `""`, so a hand-written or
|
|
23069
|
+
* CLI-written entry can be a bare string. Canonical `enabledTools` is
|
|
23070
|
+
* `string[]`, and `RulesyncMcp` throws on a schema failure — carrying a string
|
|
23071
|
+
* through would reject the WHOLE imported MCP file rather than one key. Anything
|
|
23072
|
+
* that is not a string array is therefore left under `tools` untouched, matching
|
|
23073
|
+
* `codexcli-mcp.ts`'s `isValidRenamedArray` guard on the same class of rename.
|
|
23074
|
+
*/
|
|
23075
|
+
function isStringArray(value) {
|
|
23076
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
23077
|
+
}
|
|
23078
|
+
/**
|
|
22887
23079
|
* Resolves and sets the transport type for each MCP server config.
|
|
22888
23080
|
* GitHub Copilot CLI requires the "type" field for each server. A server it
|
|
22889
23081
|
* cannot express — one that names no transport, or names one it carries no way
|
|
@@ -22927,7 +23119,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22927
23119
|
}
|
|
22928
23120
|
const { httpUrl: _httpUrl, ...rest } = parsed;
|
|
22929
23121
|
result[name] = {
|
|
22930
|
-
...
|
|
23122
|
+
...renameEnabledToolsToTools({
|
|
23123
|
+
server: rest,
|
|
23124
|
+
serverName: name,
|
|
23125
|
+
logger
|
|
23126
|
+
}),
|
|
22931
23127
|
type,
|
|
22932
23128
|
url
|
|
22933
23129
|
};
|
|
@@ -22945,7 +23141,11 @@ function addTypeField(mcpServers, logger) {
|
|
|
22945
23141
|
continue;
|
|
22946
23142
|
}
|
|
22947
23143
|
result[name] = {
|
|
22948
|
-
...
|
|
23144
|
+
...renameEnabledToolsToTools({
|
|
23145
|
+
server: local,
|
|
23146
|
+
serverName: name,
|
|
23147
|
+
logger
|
|
23148
|
+
}),
|
|
22949
23149
|
type,
|
|
22950
23150
|
command: head,
|
|
22951
23151
|
...tail.length > 0 && { args: tail }
|
|
@@ -22959,11 +23159,19 @@ function addTypeField(mcpServers, logger) {
|
|
|
22959
23159
|
function removeTypeField(config) {
|
|
22960
23160
|
const result = {};
|
|
22961
23161
|
for (const [name, server] of Object.entries(config.mcpServers ?? {})) {
|
|
22962
|
-
|
|
22963
|
-
|
|
23162
|
+
const { tools, ...withoutTools } = server;
|
|
23163
|
+
const restored = isStringArray(tools) ? {
|
|
23164
|
+
...withoutTools,
|
|
23165
|
+
enabledTools: tools
|
|
23166
|
+
} : {
|
|
23167
|
+
...withoutTools,
|
|
23168
|
+
...tools !== void 0 && { tools }
|
|
23169
|
+
};
|
|
23170
|
+
if (restored.type !== "stdio") {
|
|
23171
|
+
result[name] = restored;
|
|
22964
23172
|
continue;
|
|
22965
23173
|
}
|
|
22966
|
-
const { type: _, ...rest } =
|
|
23174
|
+
const { type: _, ...rest } = restored;
|
|
22967
23175
|
result[name] = rest;
|
|
22968
23176
|
}
|
|
22969
23177
|
return result;
|
|
@@ -23615,11 +23823,11 @@ function applyGooseStdioFields(ext, config) {
|
|
|
23615
23823
|
if (Array.isArray(command)) {
|
|
23616
23824
|
if (typeof command[0] === "string") ext.cmd = command[0];
|
|
23617
23825
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23618
|
-
const args = isStringArray$
|
|
23826
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23619
23827
|
if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
|
|
23620
23828
|
} else if (typeof command === "string") {
|
|
23621
23829
|
ext.cmd = command;
|
|
23622
|
-
if (isStringArray$
|
|
23830
|
+
if (isStringArray$2(config.args)) ext.args = config.args;
|
|
23623
23831
|
}
|
|
23624
23832
|
if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
|
|
23625
23833
|
}
|
|
@@ -23721,7 +23929,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
23721
23929
|
else if (type === "streamable_http") server.type = "http";
|
|
23722
23930
|
else server.type = "stdio";
|
|
23723
23931
|
if (typeof ext.cmd === "string") server.command = ext.cmd;
|
|
23724
|
-
if (isStringArray$
|
|
23932
|
+
if (isStringArray$2(ext.args)) server.args = ext.args;
|
|
23725
23933
|
if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
|
|
23726
23934
|
if (typeof ext.uri === "string") server.url = ext.uri;
|
|
23727
23935
|
if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
|
|
@@ -23742,11 +23950,11 @@ function buildGoosePluginStdioServer(config) {
|
|
|
23742
23950
|
if (Array.isArray(command)) {
|
|
23743
23951
|
if (typeof command[0] === "string") server.command = command[0];
|
|
23744
23952
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
23745
|
-
const args = isStringArray$
|
|
23953
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
23746
23954
|
if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
|
|
23747
23955
|
} else if (typeof command === "string") {
|
|
23748
23956
|
server.command = command;
|
|
23749
|
-
if (isStringArray$
|
|
23957
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
23750
23958
|
}
|
|
23751
23959
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
23752
23960
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -24078,7 +24286,7 @@ function copyHermesOauth(source) {
|
|
|
24078
24286
|
"client_secret"
|
|
24079
24287
|
]) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
24080
24288
|
if (typeof source.redirect_port === "number") oauth.redirect_port = source.redirect_port;
|
|
24081
|
-
if (isStringArray$
|
|
24289
|
+
if (isStringArray$2(source.scopes)) oauth.scopes = source.scopes;
|
|
24082
24290
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
24083
24291
|
}
|
|
24084
24292
|
function copyHermesAdvancedFields(source, target) {
|
|
@@ -24087,7 +24295,7 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
24087
24295
|
target.auth = source.auth;
|
|
24088
24296
|
copied = true;
|
|
24089
24297
|
}
|
|
24090
|
-
if (typeof source.client_cert === "string" || isStringArray$
|
|
24298
|
+
if (typeof source.client_cert === "string" || isStringArray$2(source.client_cert)) {
|
|
24091
24299
|
target.client_cert = source.client_cert;
|
|
24092
24300
|
copied = true;
|
|
24093
24301
|
}
|
|
@@ -24156,8 +24364,8 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
24156
24364
|
*/
|
|
24157
24365
|
function buildHermesToolsBlock(config) {
|
|
24158
24366
|
const tools = {};
|
|
24159
|
-
if (isStringArray$
|
|
24160
|
-
if (isStringArray$
|
|
24367
|
+
if (isStringArray$2(config.enabledTools)) tools.include = config.enabledTools;
|
|
24368
|
+
if (isStringArray$2(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
24161
24369
|
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
24162
24370
|
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
24163
24371
|
return tools;
|
|
@@ -24169,8 +24377,8 @@ function buildHermesToolsBlock(config) {
|
|
|
24169
24377
|
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
24170
24378
|
*/
|
|
24171
24379
|
function applyHermesToolsBlock(hermesTools, server) {
|
|
24172
|
-
if (isStringArray$
|
|
24173
|
-
if (isStringArray$
|
|
24380
|
+
if (isStringArray$2(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
24381
|
+
if (isStringArray$2(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
24174
24382
|
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
24175
24383
|
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
24176
24384
|
}
|
|
@@ -24196,11 +24404,11 @@ function convertServerToHermes(config) {
|
|
|
24196
24404
|
if (Array.isArray(command)) {
|
|
24197
24405
|
if (typeof command[0] === "string") out.command = command[0];
|
|
24198
24406
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
24199
|
-
const args = isStringArray$
|
|
24407
|
+
const args = isStringArray$2(config.args) ? config.args : [];
|
|
24200
24408
|
if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
|
|
24201
24409
|
} else if (typeof command === "string") {
|
|
24202
24410
|
out.command = command;
|
|
24203
|
-
if (isStringArray$
|
|
24411
|
+
if (isStringArray$2(config.args)) out.args = config.args;
|
|
24204
24412
|
}
|
|
24205
24413
|
if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
|
|
24206
24414
|
} else if (url !== void 0) {
|
|
@@ -24250,7 +24458,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
24250
24458
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
24251
24459
|
const server = {};
|
|
24252
24460
|
if (typeof config.command === "string") server.command = config.command;
|
|
24253
|
-
if (isStringArray$
|
|
24461
|
+
if (isStringArray$2(config.args)) server.args = config.args;
|
|
24254
24462
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
24255
24463
|
if (typeof config.url === "string") server.url = config.url;
|
|
24256
24464
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
@@ -25208,13 +25416,17 @@ function mergeToolLists(...lists) {
|
|
|
25208
25416
|
*
|
|
25209
25417
|
* Both native names are documented per-server fields, so a config that already
|
|
25210
25418
|
* spells them natively keeps working: the two lists are merged rather than
|
|
25211
|
-
* one overwriting the other.
|
|
25419
|
+
* one overwriting the other. That is the "merge" branch of the collision rule
|
|
25420
|
+
* documented on `enabledTools` in `src/types/mcp.ts` — it applies here because
|
|
25421
|
+
* the native names are redundant spellings of the same additive lists;
|
|
25422
|
+
* `copilotcli-mcp.ts` lets the canonical value win instead, and
|
|
25423
|
+
* `codexcli-mcp.ts` refuses the canonical value instead.
|
|
25212
25424
|
* @see https://kiro.dev/docs/mcp/configuration/
|
|
25213
25425
|
*/
|
|
25214
25426
|
function toKiroMcpServers(servers) {
|
|
25215
25427
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25216
25428
|
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
25217
|
-
const autoApprove = mergeToolLists(isStringArray$
|
|
25429
|
+
const autoApprove = mergeToolLists(isStringArray$2(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
25218
25430
|
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
25219
25431
|
return [name, {
|
|
25220
25432
|
...rest,
|
|
@@ -25238,7 +25450,7 @@ function fromKiroMcpServers(servers) {
|
|
|
25238
25450
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
25239
25451
|
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
25240
25452
|
const { autoApprove, ...rest } = server;
|
|
25241
|
-
if (!isStringArray$
|
|
25453
|
+
if (!isStringArray$2(autoApprove)) return [name, server];
|
|
25242
25454
|
return [name, {
|
|
25243
25455
|
...rest,
|
|
25244
25456
|
kiroAutoApprove: autoApprove
|
|
@@ -26380,7 +26592,7 @@ async function readRovodevConfigYaml({ outputRoot }) {
|
|
|
26380
26592
|
}
|
|
26381
26593
|
function disabledNamesOf(config) {
|
|
26382
26594
|
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
26383
|
-
return isStringArray$
|
|
26595
|
+
return isStringArray$2(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
26384
26596
|
}
|
|
26385
26597
|
/**
|
|
26386
26598
|
* Auxiliary writer for the `mcp:` block of `.rovodev/config.yml` (project) /
|
|
@@ -26513,7 +26725,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
26513
26725
|
return [];
|
|
26514
26726
|
}
|
|
26515
26727
|
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
26516
|
-
const existingDisabled = isStringArray$
|
|
26728
|
+
const existingDisabled = isStringArray$2(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
26517
26729
|
const managedNameSet = new Set(managedNames);
|
|
26518
26730
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
26519
26731
|
if (reEnabled.length > 0) logger?.warn(`Rovo Dev MCP: re-enabling ${reEnabled.join(", ")} — the canonical config does not mark ${reEnabled.length === 1 ? "it" : "them"} disabled. Set "disabled": true in .rulesync/mcp.jsonc to keep a managed server off.`);
|
|
@@ -27355,7 +27567,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
27355
27567
|
meta: {
|
|
27356
27568
|
supportsProject: true,
|
|
27357
27569
|
supportsGlobal: true,
|
|
27358
|
-
supportsEnabledTools:
|
|
27570
|
+
supportsEnabledTools: true,
|
|
27359
27571
|
supportsDisabledTools: false
|
|
27360
27572
|
}
|
|
27361
27573
|
}],
|
|
@@ -28179,6 +28391,34 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
28179
28391
|
//#endregion
|
|
28180
28392
|
//#region src/features/permissions/antigravity-cli-permissions.ts
|
|
28181
28393
|
/**
|
|
28394
|
+
* Top-level `~/.gemini/antigravity-cli/settings.json` keys the `antigravity-cli`
|
|
28395
|
+
* permissions override may author. They are siblings of `permissions`, passed
|
|
28396
|
+
* through verbatim in both directions.
|
|
28397
|
+
*/
|
|
28398
|
+
const ANTIGRAVITY_CLI_OVERRIDE_KEYS = [
|
|
28399
|
+
"toolPermission",
|
|
28400
|
+
"enableTerminalSandbox",
|
|
28401
|
+
"artifactReviewPolicy",
|
|
28402
|
+
"allowNonWorkspaceAccess",
|
|
28403
|
+
"agentMode"
|
|
28404
|
+
];
|
|
28405
|
+
const moduleLogger$2 = fallbackLogger;
|
|
28406
|
+
/**
|
|
28407
|
+
* Narrow an imported `settings.json` value to one of the documented values of
|
|
28408
|
+
* an enum-typed override key. The override schema declares `toolPermission`,
|
|
28409
|
+
* `artifactReviewPolicy` and `agentMode` as `z.enum`, so carrying an
|
|
28410
|
+
* unrecognized value through — a hand-written typo, or a preset a newer
|
|
28411
|
+
* Antigravity release added — would fail validation of the whole imported
|
|
28412
|
+
* `.rulesync/permissions.jsonc` instead of dropping just that key. A dropped
|
|
28413
|
+
* value is warned about rather than discarded silently, so a preset rulesync
|
|
28414
|
+
* does not know about yet is visible instead of quietly missing.
|
|
28415
|
+
*/
|
|
28416
|
+
function pickDocumentedValue({ key, value, documented }) {
|
|
28417
|
+
if (typeof value !== "string") return void 0;
|
|
28418
|
+
if (documented.includes(value)) return value;
|
|
28419
|
+
moduleLogger$2.warn(`Antigravity CLI permissions: dropping '${key}: ${value}' on import — it is not one of the documented values (${documented.join(", ")}), and carrying it through would fail validation of the whole permissions file. Author it under the 'antigravity-cli' override by hand once rulesync supports it.`);
|
|
28420
|
+
}
|
|
28421
|
+
/**
|
|
28182
28422
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
28183
28423
|
* Antigravity CLI Fine-Grained Permissions Engine action vocabulary
|
|
28184
28424
|
* (`read_file`, `write_file`, `read_url`, `command`, `mcp`, plus `execute_url` /
|
|
@@ -28253,9 +28493,10 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
28253
28493
|
* file (global scope only). The file holds other CLI settings besides
|
|
28254
28494
|
* permissions, so it is never deleted.
|
|
28255
28495
|
*
|
|
28256
|
-
*
|
|
28496
|
+
* Five CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
28257
28497
|
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
28258
|
-
* `artifactReviewPolicy` and `
|
|
28498
|
+
* `artifactReviewPolicy`, `allowNonWorkspaceAccess` and `agentMode` (the
|
|
28499
|
+
* baseline execution mode) — are authored and
|
|
28259
28500
|
* round-tripped through the `antigravity-cli` permissions override (see
|
|
28260
28501
|
* `AntigravityCliPermissionsOverrideSchema`).
|
|
28261
28502
|
*/
|
|
@@ -28319,10 +28560,10 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28319
28560
|
permissions: mergedPermissions
|
|
28320
28561
|
};
|
|
28321
28562
|
const override = config["antigravity-cli"];
|
|
28322
|
-
|
|
28323
|
-
|
|
28324
|
-
|
|
28325
|
-
|
|
28563
|
+
for (const key of ANTIGRAVITY_CLI_OVERRIDE_KEYS) {
|
|
28564
|
+
const value = override?.[key];
|
|
28565
|
+
if (value !== void 0) merged[key] = value;
|
|
28566
|
+
}
|
|
28326
28567
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
28327
28568
|
return new AntigravityCliPermissions({
|
|
28328
28569
|
outputRoot,
|
|
@@ -28347,10 +28588,26 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
28347
28588
|
deny: permissions.deny ?? []
|
|
28348
28589
|
});
|
|
28349
28590
|
const override = {};
|
|
28350
|
-
|
|
28591
|
+
const toolPermission = pickDocumentedValue({
|
|
28592
|
+
key: "toolPermission",
|
|
28593
|
+
value: settings.toolPermission,
|
|
28594
|
+
documented: ANTIGRAVITY_CLI_TOOL_PERMISSIONS
|
|
28595
|
+
});
|
|
28596
|
+
if (toolPermission !== void 0) override.toolPermission = toolPermission;
|
|
28351
28597
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
28352
|
-
|
|
28598
|
+
const artifactReviewPolicy = pickDocumentedValue({
|
|
28599
|
+
key: "artifactReviewPolicy",
|
|
28600
|
+
value: settings.artifactReviewPolicy,
|
|
28601
|
+
documented: ANTIGRAVITY_CLI_ARTIFACT_REVIEW_POLICIES
|
|
28602
|
+
});
|
|
28603
|
+
if (artifactReviewPolicy !== void 0) override.artifactReviewPolicy = artifactReviewPolicy;
|
|
28353
28604
|
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
28605
|
+
const agentMode = pickDocumentedValue({
|
|
28606
|
+
key: "agentMode",
|
|
28607
|
+
value: settings.agentMode,
|
|
28608
|
+
documented: ANTIGRAVITY_CLI_AGENT_MODES
|
|
28609
|
+
});
|
|
28610
|
+
if (agentMode !== void 0) override.agentMode = agentMode;
|
|
28354
28611
|
const result = { ...config };
|
|
28355
28612
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
28356
28613
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -29176,6 +29433,119 @@ function deepMergeRecords(base, patch) {
|
|
|
29176
29433
|
}
|
|
29177
29434
|
return merged;
|
|
29178
29435
|
}
|
|
29436
|
+
/**
|
|
29437
|
+
* `sandbox.*` paths Claude Code honors only from user settings, managed settings
|
|
29438
|
+
* and the `--settings` CLI flag. Written into a project `.claude/settings.json`
|
|
29439
|
+
* they are silently ignored, so a committed file would read as though it
|
|
29440
|
+
* enforced a sandbox policy while doing nothing — security-relevant for
|
|
29441
|
+
* `network.strictAllowlist` and the credential-masking keys in particular.
|
|
29442
|
+
* Generation therefore drops them at project scope with a per-key warning and
|
|
29443
|
+
* emits them only under `--global`.
|
|
29444
|
+
*
|
|
29445
|
+
* Deliberately NOT listed:
|
|
29446
|
+
* - `bwrapPath` / `socatPath`: v2.1.232 added them to the managed-settings
|
|
29447
|
+
* approval dialog, which is a consent prompt, not a project-scope rejection.
|
|
29448
|
+
* - `credentials.envVars` / `credentials.files`: the ignored-at-project-scope
|
|
29449
|
+
* unit is the individual entry's mode, not the settings key, and the same
|
|
29450
|
+
* lists carry `deny` entries that project settings *do* honor — dropping a
|
|
29451
|
+
* whole list would remove real restrictions. `stripProjectIgnoredMaskEntries`
|
|
29452
|
+
* filters those lists per entry instead.
|
|
29453
|
+
*
|
|
29454
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
29455
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md — v2.1.232 scoped `sandbox.ripgrep`
|
|
29456
|
+
*/
|
|
29457
|
+
const CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS = [
|
|
29458
|
+
["filesystem", "disabled"],
|
|
29459
|
+
["network", "strictAllowlist"],
|
|
29460
|
+
["network", "tlsTerminate"],
|
|
29461
|
+
["credentials", "allowPlaintextInject"],
|
|
29462
|
+
["credentials", "awsPairs"],
|
|
29463
|
+
["credentials", "sigv4"],
|
|
29464
|
+
["allowAppleEvents"],
|
|
29465
|
+
["ripgrep"]
|
|
29466
|
+
];
|
|
29467
|
+
/**
|
|
29468
|
+
* Copy of the authored `sandbox` override with the user/managed-only paths
|
|
29469
|
+
* removed, warning once per dropped path. Only the override copy is filtered —
|
|
29470
|
+
* a value already hand-written in the target file is left untouched, matching
|
|
29471
|
+
* the `qwencode` `security.allowPrivateNetworkHooks` precedent.
|
|
29472
|
+
*/
|
|
29473
|
+
function stripGlobalOnlySandboxPaths({ sandbox, relativeFilePath, logger }) {
|
|
29474
|
+
const filtered = structuredClone(sandbox);
|
|
29475
|
+
for (const path of CLAUDECODE_GLOBAL_ONLY_SANDBOX_PATHS) {
|
|
29476
|
+
const leaf = path.at(-1);
|
|
29477
|
+
if (leaf === void 0) continue;
|
|
29478
|
+
const parentPath = path.slice(0, -1);
|
|
29479
|
+
let parent = filtered;
|
|
29480
|
+
for (const segment of parentPath) {
|
|
29481
|
+
const next = parent[segment];
|
|
29482
|
+
if (!isPlainRecord(next)) {
|
|
29483
|
+
parent = {};
|
|
29484
|
+
break;
|
|
29485
|
+
}
|
|
29486
|
+
parent = next;
|
|
29487
|
+
}
|
|
29488
|
+
if (parent[leaf] === void 0) continue;
|
|
29489
|
+
delete parent[leaf];
|
|
29490
|
+
const [container] = parentPath;
|
|
29491
|
+
if (container !== void 0 && isPlainRecord(filtered[container])) {
|
|
29492
|
+
if (Object.keys(filtered[container]).length === 0) delete filtered[container];
|
|
29493
|
+
}
|
|
29494
|
+
logger?.warn(`Claude Code permissions: 'sandbox.${path.join(".")}' is only honored in user/managed/--settings settings, so it is not written to the project-scoped ${relativeFilePath}. Author it in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
29495
|
+
}
|
|
29496
|
+
return filtered;
|
|
29497
|
+
}
|
|
29498
|
+
/** The `sandbox.credentials` lists whose entries accept `"mode": "mask"`. */
|
|
29499
|
+
const CLAUDECODE_MASKABLE_CREDENTIAL_LISTS = ["envVars", "files"];
|
|
29500
|
+
/**
|
|
29501
|
+
* Copy of the authored `sandbox` override with the `credentials.envVars` /
|
|
29502
|
+
* `credentials.files` entries whose `mode` is `"mask"` removed, warning once per
|
|
29503
|
+
* list.
|
|
29504
|
+
*
|
|
29505
|
+
* Masking authorizes the sandbox proxy to send the real credential to the listed
|
|
29506
|
+
* hosts, so Claude Code honors it only from user settings, managed settings and
|
|
29507
|
+
* the `--settings` CLI flag; a `mask` entry in a repository's
|
|
29508
|
+
* `.claude/settings.json` is ignored outright. Keeping it would read as though
|
|
29509
|
+
* the credential were masked while nothing protects it — the one project-scope
|
|
29510
|
+
* gap whose consequence is a live credential leaving the sandbox unmasked.
|
|
29511
|
+
*
|
|
29512
|
+
* Filtered per entry rather than per key, because the same lists carry `deny`
|
|
29513
|
+
* entries that project settings *do* honor. `mode` is matched exactly: an entry
|
|
29514
|
+
* Claude Code cannot read as `mask` is not treated as one either (it degrades
|
|
29515
|
+
* such entries to `deny`), so the "reads as masked but isn't" state this guards
|
|
29516
|
+
* against cannot slip through a differently-spelled value.
|
|
29517
|
+
*
|
|
29518
|
+
* Like `stripGlobalOnlySandboxPaths`, only the override copy is filtered — a
|
|
29519
|
+
* value already in the target file is left untouched, which is why the warning
|
|
29520
|
+
* points at it.
|
|
29521
|
+
*
|
|
29522
|
+
* @see https://code.claude.com/docs/en/sandboxing — "`mask` entries … are all
|
|
29523
|
+
* ignored in a repository's `.claude/settings.json` or
|
|
29524
|
+
* `.claude/settings.local.json`"; the file-entry section states the same
|
|
29525
|
+
* settings-source restriction applies there.
|
|
29526
|
+
*/
|
|
29527
|
+
function stripProjectIgnoredMaskEntries({ sandbox, relativeFilePath, logger }) {
|
|
29528
|
+
const credentials = sandbox.credentials;
|
|
29529
|
+
if (!isPlainRecord(credentials)) return sandbox;
|
|
29530
|
+
const filteredCredentials = { ...credentials };
|
|
29531
|
+
let changed = false;
|
|
29532
|
+
for (const listKey of CLAUDECODE_MASKABLE_CREDENTIAL_LISTS) {
|
|
29533
|
+
const list = filteredCredentials[listKey];
|
|
29534
|
+
if (!Array.isArray(list)) continue;
|
|
29535
|
+
const kept = list.filter((entry) => !(isPlainRecord(entry) && entry.mode === "mask"));
|
|
29536
|
+
if (kept.length === list.length) continue;
|
|
29537
|
+
changed = true;
|
|
29538
|
+
const dropped = list.length - kept.length;
|
|
29539
|
+
logger?.warn(`Claude Code permissions: 'sandbox.credentials.${listKey}' entries with 'mode: "mask"' are only honored in user/managed/--settings settings, so ${dropped} of them ${dropped === 1 ? "was" : "were"} not written to the project-scoped ${relativeFilePath}. Author them in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
29540
|
+
if (kept.length === 0) delete filteredCredentials[listKey];
|
|
29541
|
+
else filteredCredentials[listKey] = kept;
|
|
29542
|
+
}
|
|
29543
|
+
if (!changed) return sandbox;
|
|
29544
|
+
const filtered = { ...sandbox };
|
|
29545
|
+
if (Object.keys(filteredCredentials).length === 0) delete filtered.credentials;
|
|
29546
|
+
else filtered.credentials = filteredCredentials;
|
|
29547
|
+
return filtered;
|
|
29548
|
+
}
|
|
29179
29549
|
const CLAUDE_PATH_RULE_ALIASES = {
|
|
29180
29550
|
Write: "Edit",
|
|
29181
29551
|
NotebookEdit: "Edit",
|
|
@@ -29229,7 +29599,7 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29229
29599
|
validate
|
|
29230
29600
|
});
|
|
29231
29601
|
}
|
|
29232
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
|
|
29602
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
29233
29603
|
const paths = ClaudecodePermissions.getSettablePaths();
|
|
29234
29604
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
29235
29605
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -29253,7 +29623,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
29253
29623
|
};
|
|
29254
29624
|
}
|
|
29255
29625
|
const overrideSandbox = config.claudecode?.sandbox;
|
|
29256
|
-
if (isPlainRecord(overrideSandbox))
|
|
29626
|
+
if (isPlainRecord(overrideSandbox)) {
|
|
29627
|
+
const scopedSandbox = global ? overrideSandbox : stripProjectIgnoredMaskEntries({
|
|
29628
|
+
sandbox: stripGlobalOnlySandboxPaths({
|
|
29629
|
+
sandbox: overrideSandbox,
|
|
29630
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29631
|
+
logger
|
|
29632
|
+
}),
|
|
29633
|
+
relativeFilePath: paths.relativeFilePath,
|
|
29634
|
+
logger
|
|
29635
|
+
});
|
|
29636
|
+
if (Object.keys(scopedSandbox).length > 0) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, scopedSandbox);
|
|
29637
|
+
}
|
|
29257
29638
|
const managedToolNames = managedClaudeToolNames(config);
|
|
29258
29639
|
const merged = applyPermissions({
|
|
29259
29640
|
settings,
|
|
@@ -31430,7 +31811,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
31430
31811
|
function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
31431
31812
|
const permission = {};
|
|
31432
31813
|
for (const key of GOOSE_PERMISSION_LIST_KEYS) {
|
|
31433
|
-
const toolNames = isStringArray$
|
|
31814
|
+
const toolNames = isStringArray$2(userPermission[key]) ? userPermission[key] : [];
|
|
31434
31815
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
31435
31816
|
for (const toolName of toolNames) {
|
|
31436
31817
|
const category = GOOSE_TO_RULESYNC_TOOL_NAME[toolName] ?? toolName;
|
|
@@ -31723,7 +32104,7 @@ const ACTION_RANK = {
|
|
|
31723
32104
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
31724
32105
|
*/
|
|
31725
32106
|
function unmanagedEntries(existingPermission, key) {
|
|
31726
|
-
return (isStringArray$
|
|
32107
|
+
return (isStringArray$2(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
31727
32108
|
}
|
|
31728
32109
|
/**
|
|
31729
32110
|
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
@@ -31766,9 +32147,9 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
31766
32147
|
* described more than once resolves to the strictest action.
|
|
31767
32148
|
*/
|
|
31768
32149
|
function parseGrokPermissionArrays(permission) {
|
|
31769
|
-
const allow = isStringArray$
|
|
31770
|
-
const deny = isStringArray$
|
|
31771
|
-
const ask = isStringArray$
|
|
32150
|
+
const allow = isStringArray$2(permission.allow) ? permission.allow : void 0;
|
|
32151
|
+
const deny = isStringArray$2(permission.deny) ? permission.deny : void 0;
|
|
32152
|
+
const ask = isStringArray$2(permission.ask) ? permission.ask : void 0;
|
|
31772
32153
|
const rules = Array.isArray(permission[GROKCLI_PERMISSION_RULES_KEY]) ? permission[GROKCLI_PERMISSION_RULES_KEY] : void 0;
|
|
31773
32154
|
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
31774
32155
|
const result = {};
|
|
@@ -31950,20 +32331,20 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
31950
32331
|
const permission = clonePermissionBlock(provenance.permission);
|
|
31951
32332
|
reconcileCommandAllowlist({
|
|
31952
32333
|
permission,
|
|
31953
|
-
commandAllowlist: isStringArray$
|
|
32334
|
+
commandAllowlist: isStringArray$2(config.command_allowlist) ? config.command_allowlist : []
|
|
31954
32335
|
});
|
|
31955
32336
|
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
31956
32337
|
reconcileNativeDenies({
|
|
31957
32338
|
permission,
|
|
31958
32339
|
category: "bash",
|
|
31959
|
-
patterns: isStringArray$
|
|
32340
|
+
patterns: isStringArray$2(approvals.deny) ? approvals.deny : []
|
|
31960
32341
|
});
|
|
31961
32342
|
const security = isRecord$1(config.security) ? config.security : {};
|
|
31962
32343
|
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
31963
32344
|
reconcileNativeDenies({
|
|
31964
32345
|
permission,
|
|
31965
32346
|
category: "webfetch",
|
|
31966
|
-
patterns: websiteBlocklist.enabled === true && isStringArray$
|
|
32347
|
+
patterns: websiteBlocklist.enabled === true && isStringArray$2(websiteBlocklist.domains) ? websiteBlocklist.domains : []
|
|
31967
32348
|
});
|
|
31968
32349
|
removeEmptyCategories(permission);
|
|
31969
32350
|
const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
|
|
@@ -33391,6 +33772,115 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
33391
33772
|
}
|
|
33392
33773
|
};
|
|
33393
33774
|
//#endregion
|
|
33775
|
+
//#region src/features/permissions/pi-permissions.ts
|
|
33776
|
+
/** The one `settings.json` key this feature owns. */
|
|
33777
|
+
const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
33778
|
+
/**
|
|
33779
|
+
* Permissions generator for Pi Coding Agent.
|
|
33780
|
+
*
|
|
33781
|
+
* Pi exposes no allow/ask/deny rule surface, so no canonical permission
|
|
33782
|
+
* category maps onto it. Its one repository-syncable tool gate is `defaultTools` —
|
|
33783
|
+
* the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
|
|
33784
|
+
* permissions override.
|
|
33785
|
+
*
|
|
33786
|
+
* Scope semantics are the **opposite** of the union-merge most targets use:
|
|
33787
|
+
* a project `defaultTools` array *replaces* the global array rather than adding
|
|
33788
|
+
* to it, so the two scopes are written independently and never combined.
|
|
33789
|
+
* Project scope writes `.pi/settings.json`; global scope writes
|
|
33790
|
+
* `~/.pi/agent/settings.json`.
|
|
33791
|
+
*
|
|
33792
|
+
* The file is hand-edited and holds many unrelated keys (`theme`,
|
|
33793
|
+
* `defaultModel`, `packages`, `sessionDir`, ...), so writes go through the
|
|
33794
|
+
* shared-config gateway with `defaultTools` as the only owned key, and the file
|
|
33795
|
+
* is never deleted.
|
|
33796
|
+
*
|
|
33797
|
+
* @see https://pi.dev/docs/latest/settings
|
|
33798
|
+
*/
|
|
33799
|
+
var PiPermissions = class PiPermissions extends ToolPermissions {
|
|
33800
|
+
constructor(params) {
|
|
33801
|
+
super({
|
|
33802
|
+
...params,
|
|
33803
|
+
fileContent: params.fileContent ?? "{}"
|
|
33804
|
+
});
|
|
33805
|
+
}
|
|
33806
|
+
/** `settings.json` holds unrelated user settings, so it must not be deleted. */
|
|
33807
|
+
isDeletable() {
|
|
33808
|
+
return false;
|
|
33809
|
+
}
|
|
33810
|
+
static getSettablePaths({ global = false } = {}) {
|
|
33811
|
+
return {
|
|
33812
|
+
relativeDirPath: global ? PI_AGENT_DIR_PATH : ".pi",
|
|
33813
|
+
relativeFilePath: PI_SETTINGS_FILE_NAME
|
|
33814
|
+
};
|
|
33815
|
+
}
|
|
33816
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
33817
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33818
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
33819
|
+
return new PiPermissions({
|
|
33820
|
+
outputRoot,
|
|
33821
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33822
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33823
|
+
fileContent,
|
|
33824
|
+
validate,
|
|
33825
|
+
global
|
|
33826
|
+
});
|
|
33827
|
+
}
|
|
33828
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
33829
|
+
const paths = PiPermissions.getSettablePaths({ global });
|
|
33830
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
33831
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
33832
|
+
const defaultTools = rulesyncPermissions.getJson().pi?.defaultTools;
|
|
33833
|
+
const patch = {};
|
|
33834
|
+
if (defaultTools !== void 0) patch[DEFAULT_TOOLS_KEY] = defaultTools;
|
|
33835
|
+
return new PiPermissions({
|
|
33836
|
+
outputRoot,
|
|
33837
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33838
|
+
relativeFilePath: paths.relativeFilePath,
|
|
33839
|
+
fileContent: applySharedConfigPatch({
|
|
33840
|
+
fileKey: sharedConfigFileKey(paths),
|
|
33841
|
+
feature: "permissions",
|
|
33842
|
+
existingContent,
|
|
33843
|
+
patch,
|
|
33844
|
+
filePath
|
|
33845
|
+
}),
|
|
33846
|
+
validate: true,
|
|
33847
|
+
global
|
|
33848
|
+
});
|
|
33849
|
+
}
|
|
33850
|
+
toRulesyncPermissions() {
|
|
33851
|
+
const defaultTools = this.parseSettings()[DEFAULT_TOOLS_KEY];
|
|
33852
|
+
const config = { permission: {} };
|
|
33853
|
+
if (Array.isArray(defaultTools) && defaultTools.every((tool) => typeof tool === "string")) config.pi = { [DEFAULT_TOOLS_KEY]: defaultTools };
|
|
33854
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
33855
|
+
}
|
|
33856
|
+
parseSettings() {
|
|
33857
|
+
try {
|
|
33858
|
+
return parseSharedConfig({
|
|
33859
|
+
format: "json",
|
|
33860
|
+
fileContent: this.getFileContent()
|
|
33861
|
+
});
|
|
33862
|
+
} catch (error) {
|
|
33863
|
+
throw new Error(`Failed to parse Pi settings in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
33864
|
+
}
|
|
33865
|
+
}
|
|
33866
|
+
validate() {
|
|
33867
|
+
return {
|
|
33868
|
+
success: true,
|
|
33869
|
+
error: null
|
|
33870
|
+
};
|
|
33871
|
+
}
|
|
33872
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
33873
|
+
return new PiPermissions({
|
|
33874
|
+
outputRoot,
|
|
33875
|
+
relativeDirPath,
|
|
33876
|
+
relativeFilePath,
|
|
33877
|
+
fileContent: "{}",
|
|
33878
|
+
validate: false,
|
|
33879
|
+
global
|
|
33880
|
+
});
|
|
33881
|
+
}
|
|
33882
|
+
};
|
|
33883
|
+
//#endregion
|
|
33394
33884
|
//#region src/features/permissions/qwencode-permissions.ts
|
|
33395
33885
|
/**
|
|
33396
33886
|
* Qwen Code uses a settings.json file in `.qwen/` (project) or `~/.qwen/` (global).
|
|
@@ -34454,7 +34944,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
34454
34944
|
permission[category] ??= {};
|
|
34455
34945
|
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
34456
34946
|
}
|
|
34457
|
-
if (isStringArray$
|
|
34947
|
+
if (isStringArray$2(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
34458
34948
|
permission.read ??= {};
|
|
34459
34949
|
permission.read[path] ??= "allow";
|
|
34460
34950
|
}
|
|
@@ -35431,8 +35921,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
35431
35921
|
const executionProfiles = isRecord$1(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
35432
35922
|
const defaultProfile = executionProfiles && isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
35433
35923
|
const config = convertWarpToRulesyncPermissions({
|
|
35434
|
-
allow: defaultProfile ? isStringArray$
|
|
35435
|
-
deny: defaultProfile ? isStringArray$
|
|
35924
|
+
allow: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$2(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
35925
|
+
deny: defaultProfile ? isStringArray$2(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$2(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
35436
35926
|
});
|
|
35437
35927
|
const warpOverride = {};
|
|
35438
35928
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
@@ -36144,6 +36634,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
36144
36634
|
supportsImport: true
|
|
36145
36635
|
}
|
|
36146
36636
|
}],
|
|
36637
|
+
["pi", {
|
|
36638
|
+
class: PiPermissions,
|
|
36639
|
+
meta: {
|
|
36640
|
+
supportsProject: true,
|
|
36641
|
+
supportsGlobal: true,
|
|
36642
|
+
supportsImport: true
|
|
36643
|
+
}
|
|
36644
|
+
}],
|
|
36147
36645
|
["qwencode", {
|
|
36148
36646
|
class: QwencodePermissions,
|
|
36149
36647
|
meta: {
|
|
@@ -38455,6 +38953,7 @@ const CursorSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
38455
38953
|
description: zod_mini.z.string(),
|
|
38456
38954
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
38457
38955
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38956
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38458
38957
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
38459
38958
|
});
|
|
38460
38959
|
/**
|
|
@@ -38509,6 +39008,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38509
39008
|
const cursorSection = {
|
|
38510
39009
|
...frontmatter.paths !== void 0 && { paths: frontmatter.paths },
|
|
38511
39010
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
39011
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
38512
39012
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
38513
39013
|
};
|
|
38514
39014
|
const rulesyncFrontmatter = {
|
|
@@ -38536,11 +39036,16 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
38536
39036
|
rootFrontmatter: rulesyncFrontmatter,
|
|
38537
39037
|
section: cursorSection
|
|
38538
39038
|
});
|
|
39039
|
+
const resolvedUserInvocable = resolveUserInvocable({
|
|
39040
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
39041
|
+
section: cursorSection
|
|
39042
|
+
});
|
|
38539
39043
|
const cursorFrontmatter = {
|
|
38540
39044
|
name: rulesyncFrontmatter.name,
|
|
38541
39045
|
description: rulesyncFrontmatter.description,
|
|
38542
39046
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
38543
39047
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
39048
|
+
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
38544
39049
|
...cursorSection?.metadata !== void 0 && { metadata: cursorSection.metadata }
|
|
38545
39050
|
};
|
|
38546
39051
|
return new CursorSkill({
|
|
@@ -46543,6 +47048,23 @@ var TaktSubagent = class TaktSubagent extends ToolSubagent {
|
|
|
46543
47048
|
};
|
|
46544
47049
|
//#endregion
|
|
46545
47050
|
//#region src/features/subagents/vibe-subagent.ts
|
|
47051
|
+
/**
|
|
47052
|
+
* Where Vibe reads a custom system prompt from: `.vibe/prompts/<id>.md`
|
|
47053
|
+
* (project) and `~/.vibe/prompts/<id>.md` (user), with the custom directories
|
|
47054
|
+
* winning over the builtins. The id must be a bare file name with no path
|
|
47055
|
+
* separators.
|
|
47056
|
+
* @see vibe/core/prompts/__init__.py
|
|
47057
|
+
*/
|
|
47058
|
+
const VIBE_PROMPTS_DIR_PATH = (0, node_path.join)(".vibe", "prompts");
|
|
47059
|
+
/**
|
|
47060
|
+
* Vibe resolves a `system_prompt_id` by file name, and
|
|
47061
|
+
* `VibeConfigSchema._check_system_prompt` evaluates it during validation — an
|
|
47062
|
+
* unresolvable id raises and the agent is dropped at discovery. The id must
|
|
47063
|
+
* therefore stay a bare slug with no path separators.
|
|
47064
|
+
*/
|
|
47065
|
+
function promptSlug(relativeFilePath) {
|
|
47066
|
+
return (0, node_path.basename)(relativeFilePath).replace(/\.(md|toml)$/, "").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
47067
|
+
}
|
|
46546
47068
|
const VibeSubagentTomlSchema = zod_mini.z.looseObject({
|
|
46547
47069
|
agent_type: zod_mini.z.enum(["agent", "subagent"]),
|
|
46548
47070
|
display_name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -46559,14 +47081,22 @@ const VibeSubagentTomlSchema = zod_mini.z.looseObject({
|
|
|
46559
47081
|
});
|
|
46560
47082
|
var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
46561
47083
|
body;
|
|
46562
|
-
|
|
46563
|
-
|
|
47084
|
+
promptFile;
|
|
47085
|
+
resolvedSystemPrompt;
|
|
47086
|
+
constructor({ body, promptFile = false, resolvedSystemPrompt, ...rest }) {
|
|
47087
|
+
if (rest.validate !== false && !promptFile) try {
|
|
46564
47088
|
VibeSubagentTomlSchema.parse(smol_toml.parse(body));
|
|
46565
47089
|
} catch (error) {
|
|
46566
47090
|
throw new Error(`Invalid TOML in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
46567
47091
|
}
|
|
46568
47092
|
super({ ...rest });
|
|
46569
47093
|
this.body = body;
|
|
47094
|
+
this.promptFile = promptFile;
|
|
47095
|
+
this.resolvedSystemPrompt = resolvedSystemPrompt;
|
|
47096
|
+
}
|
|
47097
|
+
/** True for the companion `.vibe/prompts/<id>.md` file. */
|
|
47098
|
+
isPromptFile() {
|
|
47099
|
+
return this.promptFile;
|
|
46570
47100
|
}
|
|
46571
47101
|
static getSettablePaths(_options = {}) {
|
|
46572
47102
|
return { relativeDirPath: (0, node_path.join)(".vibe", "agents") };
|
|
@@ -46581,7 +47111,9 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46581
47111
|
} catch (error) {
|
|
46582
47112
|
throw new Error(`Failed to parse TOML in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
46583
47113
|
}
|
|
46584
|
-
const { system_prompt, description, display_name, ...vibeSection } = parsed;
|
|
47114
|
+
const { system_prompt, system_prompt_id, description, display_name, ...vibeSection } = parsed;
|
|
47115
|
+
const resolvedBody = this.resolvedSystemPrompt ?? system_prompt;
|
|
47116
|
+
const unresolvedPromptId = this.resolvedSystemPrompt === void 0 ? system_prompt_id : void 0;
|
|
46585
47117
|
const fileStem = (0, node_path.basename)(this.getRelativeFilePath(), ".toml");
|
|
46586
47118
|
const rulesyncFrontmatter = {
|
|
46587
47119
|
targets: ["vibe"],
|
|
@@ -46590,13 +47122,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46590
47122
|
vibe: {
|
|
46591
47123
|
...display_name !== void 0 && { display_name },
|
|
46592
47124
|
...description !== void 0 && { description },
|
|
47125
|
+
...unresolvedPromptId !== void 0 && { system_prompt_id: unresolvedPromptId },
|
|
46593
47126
|
...vibeSection
|
|
46594
47127
|
}
|
|
46595
47128
|
};
|
|
46596
47129
|
return new RulesyncSubagent({
|
|
46597
47130
|
outputRoot: this.outputRoot,
|
|
46598
47131
|
frontmatter: rulesyncFrontmatter,
|
|
46599
|
-
body:
|
|
47132
|
+
body: resolvedBody ?? "",
|
|
46600
47133
|
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
46601
47134
|
relativeFilePath: this.getRelativeFilePath().replace(/\.toml$/, ".md"),
|
|
46602
47135
|
validate: true
|
|
@@ -46611,12 +47144,14 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46611
47144
|
"description",
|
|
46612
47145
|
"system_prompt"
|
|
46613
47146
|
]);
|
|
47147
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47148
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
46614
47149
|
const tomlObj = {
|
|
46615
47150
|
agent_type: rawSection.agent_type === "agent" ? "agent" : "subagent",
|
|
46616
47151
|
display_name: typeof rawSection.display_name === "string" ? rawSection.display_name : frontmatter.name,
|
|
46617
47152
|
...frontmatter.description ? { description: frontmatter.description } : {},
|
|
46618
|
-
...
|
|
46619
|
-
...
|
|
47153
|
+
...vibeSection,
|
|
47154
|
+
...promptBody ? { system_prompt_id: slug } : {}
|
|
46620
47155
|
};
|
|
46621
47156
|
const body = smol_toml.stringify(tomlObj);
|
|
46622
47157
|
const paths = this.getSettablePaths({ global });
|
|
@@ -46631,6 +47166,36 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46631
47166
|
global
|
|
46632
47167
|
});
|
|
46633
47168
|
}
|
|
47169
|
+
/**
|
|
47170
|
+
* Emits the agent TOML plus its companion prompt file. The prompt file MUST
|
|
47171
|
+
* be written together with the id: `VibeConfigSchema._check_system_prompt`
|
|
47172
|
+
* evaluates the property during validation, so an unresolvable
|
|
47173
|
+
* `system_prompt_id` raises and `AgentRegistry._try_load` drops the agent
|
|
47174
|
+
* with a warning.
|
|
47175
|
+
*/
|
|
47176
|
+
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false }) {
|
|
47177
|
+
return rulesyncSubagents.flatMap((rulesyncSubagent) => {
|
|
47178
|
+
const agentFile = VibeSubagent.fromRulesyncSubagent({
|
|
47179
|
+
outputRoot,
|
|
47180
|
+
relativeDirPath: VibeSubagent.getSettablePaths({ global }).relativeDirPath,
|
|
47181
|
+
rulesyncSubagent,
|
|
47182
|
+
global
|
|
47183
|
+
});
|
|
47184
|
+
const promptBody = rulesyncSubagent.getBody();
|
|
47185
|
+
if (!promptBody) return [agentFile];
|
|
47186
|
+
const slug = promptSlug(rulesyncSubagent.getRelativeFilePath());
|
|
47187
|
+
return [agentFile, new VibeSubagent({
|
|
47188
|
+
outputRoot,
|
|
47189
|
+
body: promptBody,
|
|
47190
|
+
relativeDirPath: VIBE_PROMPTS_DIR_PATH,
|
|
47191
|
+
relativeFilePath: `${slug}.md`,
|
|
47192
|
+
fileContent: promptBody,
|
|
47193
|
+
promptFile: true,
|
|
47194
|
+
validate: false,
|
|
47195
|
+
global
|
|
47196
|
+
})];
|
|
47197
|
+
});
|
|
47198
|
+
}
|
|
46634
47199
|
validate() {
|
|
46635
47200
|
try {
|
|
46636
47201
|
VibeSubagentTomlSchema.parse(smol_toml.parse(this.body));
|
|
@@ -46655,6 +47220,10 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46655
47220
|
const paths = this.getSettablePaths({ global });
|
|
46656
47221
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
46657
47222
|
const fileContent = await readFileContent(filePath);
|
|
47223
|
+
const resolvedSystemPrompt = await VibeSubagent.readSystemPrompt({
|
|
47224
|
+
outputRoot,
|
|
47225
|
+
fileContent
|
|
47226
|
+
});
|
|
46658
47227
|
const subagent = new VibeSubagent({
|
|
46659
47228
|
outputRoot,
|
|
46660
47229
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -46662,7 +47231,8 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46662
47231
|
body: fileContent.trim(),
|
|
46663
47232
|
fileContent,
|
|
46664
47233
|
validate,
|
|
46665
|
-
global
|
|
47234
|
+
global,
|
|
47235
|
+
...resolvedSystemPrompt !== void 0 && { resolvedSystemPrompt }
|
|
46666
47236
|
});
|
|
46667
47237
|
if (validate) {
|
|
46668
47238
|
const result = subagent.validate();
|
|
@@ -46670,6 +47240,21 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
46670
47240
|
}
|
|
46671
47241
|
return subagent;
|
|
46672
47242
|
}
|
|
47243
|
+
/**
|
|
47244
|
+
* Read `.vibe/prompts/<id>.md` for an agent that names a `system_prompt_id`.
|
|
47245
|
+
* Returns `undefined` when there is no id or the file is missing, in which
|
|
47246
|
+
* case import falls back to the legacy `system_prompt` key.
|
|
47247
|
+
*/
|
|
47248
|
+
static async readSystemPrompt({ outputRoot, fileContent }) {
|
|
47249
|
+
let promptId;
|
|
47250
|
+
try {
|
|
47251
|
+
promptId = VibeSubagentTomlSchema.parse(smol_toml.parse(fileContent)).system_prompt_id;
|
|
47252
|
+
} catch {
|
|
47253
|
+
return;
|
|
47254
|
+
}
|
|
47255
|
+
if (typeof promptId !== "string" || promptId === "" || /[\\/]/.test(promptId)) return;
|
|
47256
|
+
return await readFileContentOrNull((0, node_path.join)(outputRoot, VIBE_PROMPTS_DIR_PATH, `${promptId}.md`)) ?? void 0;
|
|
47257
|
+
}
|
|
46673
47258
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
46674
47259
|
return new VibeSubagent({
|
|
46675
47260
|
outputRoot,
|
|
@@ -46712,7 +47297,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46712
47297
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
46713
47298
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
46714
47299
|
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
46715
|
-
if (isStringArray$
|
|
47300
|
+
if (isStringArray$2(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
46716
47301
|
return mode;
|
|
46717
47302
|
}
|
|
46718
47303
|
toRulesyncSubagents() {
|
|
@@ -46724,7 +47309,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
46724
47309
|
targets: ["zoocode"],
|
|
46725
47310
|
roo: restRooSection
|
|
46726
47311
|
};
|
|
46727
|
-
if (isStringArray$
|
|
47312
|
+
if (isStringArray$2(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
46728
47313
|
return new RulesyncSubagent({
|
|
46729
47314
|
outputRoot: ".",
|
|
46730
47315
|
frontmatter: rebuilt,
|
|
@@ -49627,8 +50212,13 @@ const STRATEGIES = [
|
|
|
49627
50212
|
* file per rule under `.devin/rules/*.md` (Devin Desktop Cascade) with YAML
|
|
49628
50213
|
* frontmatter carrying a `trigger` (always_on | glob | manual | model_decision)
|
|
49629
50214
|
* plus companion `globs`/`description` fields.
|
|
49630
|
-
* - Global scope:
|
|
49631
|
-
*
|
|
50215
|
+
* - Global scope: mirrors the project layout. The root rule is a single
|
|
50216
|
+
* plain-markdown, always-on file (no frontmatter) at
|
|
50217
|
+
* `~/.config/devin/AGENTS.md`; non-root rules are one file per rule under
|
|
50218
|
+
* `~/.devin/rules/*.md` with the same `trigger`/`globs` frontmatter as project
|
|
50219
|
+
* scope. Note the directory split — the per-rule global directory is the home
|
|
50220
|
+
* `~/.devin/`, not the `~/.config/devin/` tree every other Devin global
|
|
50221
|
+
* surface uses. That is what the rules page documents.
|
|
49632
50222
|
*
|
|
49633
50223
|
* Trigger inference (when no explicit devin trigger is persisted):
|
|
49634
50224
|
* - Specific globs (non wildcard) → glob
|
|
@@ -49659,19 +50249,32 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49659
50249
|
relativeFilePath: DEVIN_GLOBAL_AGENTS_FILE_NAME
|
|
49660
50250
|
};
|
|
49661
50251
|
}
|
|
50252
|
+
/**
|
|
50253
|
+
* The always-on root rule file for a scope: the project-root `AGENTS.md`
|
|
50254
|
+
* Devin CLI / Devin Local reads, or `~/.config/devin/AGENTS.md` in global
|
|
50255
|
+
* scope. Single-sourced so `getSettablePaths`, `fromFile` and
|
|
50256
|
+
* `fromRulesyncRule` cannot disagree about it.
|
|
50257
|
+
*/
|
|
50258
|
+
static getRootPath(global, excludeToolDir) {
|
|
50259
|
+
return global ? DevinRule.getGlobalRootPath(excludeToolDir) : {
|
|
50260
|
+
relativeDirPath: ".",
|
|
50261
|
+
relativeFilePath: "AGENTS.md"
|
|
50262
|
+
};
|
|
50263
|
+
}
|
|
49662
50264
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
49663
|
-
if (global) return {
|
|
50265
|
+
if (global) return {
|
|
50266
|
+
root: DevinRule.getRootPath(true, excludeToolDir),
|
|
50267
|
+
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
50268
|
+
};
|
|
49664
50269
|
return {
|
|
49665
|
-
root:
|
|
49666
|
-
relativeDirPath: ".",
|
|
49667
|
-
relativeFilePath: "AGENTS.md"
|
|
49668
|
-
},
|
|
50270
|
+
root: DevinRule.getRootPath(false, excludeToolDir),
|
|
49669
50271
|
nonRoot: { relativeDirPath: buildToolPath(DEVIN_DIR, "rules", excludeToolDir) }
|
|
49670
50272
|
};
|
|
49671
50273
|
}
|
|
49672
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
49673
|
-
|
|
49674
|
-
|
|
50274
|
+
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
50275
|
+
const nonRootDirPath = buildToolPath(DEVIN_DIR, "rules");
|
|
50276
|
+
if (relativeDirPath !== void 0 ? relativeDirPath !== nonRootDirPath : relativeFilePath === DevinRule.getRootPath(global).relativeFilePath) {
|
|
50277
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49675
50278
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootPath.relativeDirPath, rootPath.relativeFilePath));
|
|
49676
50279
|
return new DevinRule({
|
|
49677
50280
|
outputRoot,
|
|
@@ -49683,19 +50286,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49683
50286
|
root: true
|
|
49684
50287
|
});
|
|
49685
50288
|
}
|
|
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
50289
|
const filePath = (0, node_path.join)(outputRoot, nonRootDirPath, relativeFilePath);
|
|
49700
50290
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
49701
50291
|
let parsedFrontmatter;
|
|
@@ -49715,8 +50305,9 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49715
50305
|
});
|
|
49716
50306
|
}
|
|
49717
50307
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
49718
|
-
|
|
49719
|
-
|
|
50308
|
+
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
50309
|
+
if (rulesyncFrontmatter.root) {
|
|
50310
|
+
const rootPath = DevinRule.getRootPath(global);
|
|
49720
50311
|
return new DevinRule({
|
|
49721
50312
|
outputRoot,
|
|
49722
50313
|
relativeDirPath: rootPath.relativeDirPath,
|
|
@@ -49727,16 +50318,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49727
50318
|
root: true
|
|
49728
50319
|
});
|
|
49729
50320
|
}
|
|
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
50321
|
const storedDevin = rulesyncFrontmatter.devin;
|
|
49741
50322
|
const normalized = normalizeStoredDevin(storedDevin);
|
|
49742
50323
|
const storedTrigger = storedDevin?.trigger;
|
|
@@ -49796,7 +50377,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49796
50377
|
error: null
|
|
49797
50378
|
};
|
|
49798
50379
|
}
|
|
49799
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath
|
|
50380
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
49800
50381
|
return new DevinRule({
|
|
49801
50382
|
outputRoot,
|
|
49802
50383
|
relativeDirPath,
|
|
@@ -49804,7 +50385,7 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
49804
50385
|
frontmatter: {},
|
|
49805
50386
|
body: "",
|
|
49806
50387
|
validate: false,
|
|
49807
|
-
root:
|
|
50388
|
+
root: relativeDirPath !== buildToolPath(DEVIN_DIR, "rules")
|
|
49808
50389
|
});
|
|
49809
50390
|
}
|
|
49810
50391
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -50437,6 +51018,11 @@ var KimiCodeRule = class KimiCodeRule extends ToolRule {
|
|
|
50437
51018
|
};
|
|
50438
51019
|
//#endregion
|
|
50439
51020
|
//#region src/features/rules/kiro-rule.ts
|
|
51021
|
+
/**
|
|
51022
|
+
* Kiro loads `AGENTS.md` as steering context from any directory in the
|
|
51023
|
+
* workspace tree (CLI 2.18.0 / IDE 1.0.309), not just the workspace root.
|
|
51024
|
+
*/
|
|
51025
|
+
const KIRO_NESTED_STEERING_FILE_NAME = "AGENTS.md";
|
|
50440
51026
|
const WILDCARD_GLOBS = /* @__PURE__ */ new Set([
|
|
50441
51027
|
"**/*",
|
|
50442
51028
|
"**",
|
|
@@ -50510,8 +51096,17 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50510
51096
|
};
|
|
50511
51097
|
return { nonRoot: { relativeDirPath: steeringDirPath } };
|
|
50512
51098
|
}
|
|
50513
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
51099
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
50514
51100
|
const paths = this.getSettablePaths({ global });
|
|
51101
|
+
const steeringDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
51102
|
+
if (overrideDirPath !== void 0 && overrideDirPath !== steeringDirPath && overrideDirPath !== "." && relativeFilePath === KIRO_NESTED_STEERING_FILE_NAME) return new KiroRule({
|
|
51103
|
+
outputRoot,
|
|
51104
|
+
relativeDirPath: overrideDirPath,
|
|
51105
|
+
relativeFilePath,
|
|
51106
|
+
fileContent: await readFileContent((0, node_path.join)(outputRoot, overrideDirPath, relativeFilePath)),
|
|
51107
|
+
validate,
|
|
51108
|
+
root: false
|
|
51109
|
+
});
|
|
50515
51110
|
const isRoot = "root" in paths && relativeFilePath === paths.root.relativeFilePath;
|
|
50516
51111
|
const relativeDirPath = paths.nonRoot?.relativeDirPath ?? buildToolPath(".kiro", "steering");
|
|
50517
51112
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
@@ -50535,6 +51130,12 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50535
51130
|
});
|
|
50536
51131
|
if (params.root) return new KiroRule(params);
|
|
50537
51132
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
51133
|
+
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
51134
|
+
if (!global && subprojectPath) return new KiroRule({
|
|
51135
|
+
...params,
|
|
51136
|
+
relativeDirPath: (0, node_path.join)(subprojectPath),
|
|
51137
|
+
relativeFilePath: KIRO_NESTED_STEERING_FILE_NAME
|
|
51138
|
+
});
|
|
50538
51139
|
const inclusion = deriveKiroInclusion({
|
|
50539
51140
|
kiro: frontmatter.kiro,
|
|
50540
51141
|
globs: frontmatter.globs
|
|
@@ -50545,7 +51146,34 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50545
51146
|
fileContent: stringifyFrontmatter(rulesyncRule.getBody(), inclusion)
|
|
50546
51147
|
});
|
|
50547
51148
|
}
|
|
51149
|
+
/**
|
|
51150
|
+
* The subproject directory a nested `AGENTS.md` scopes, or `undefined` for the
|
|
51151
|
+
* root rule and for the `.kiro/steering/` files.
|
|
51152
|
+
*/
|
|
51153
|
+
getSubprojectPath() {
|
|
51154
|
+
if (this.isRoot() || this.getRelativeFilePath() !== KIRO_NESTED_STEERING_FILE_NAME) return;
|
|
51155
|
+
const relativeDirPath = toPosixPath(this.getRelativeDirPath());
|
|
51156
|
+
if (relativeDirPath === "." || relativeDirPath === "" || relativeDirPath.startsWith(".")) return;
|
|
51157
|
+
return relativeDirPath;
|
|
51158
|
+
}
|
|
50548
51159
|
toRulesyncRule() {
|
|
51160
|
+
const subprojectPath = this.getSubprojectPath();
|
|
51161
|
+
if (subprojectPath !== void 0) {
|
|
51162
|
+
const slug = subprojectPath.replaceAll("/", "-");
|
|
51163
|
+
return new RulesyncRule({
|
|
51164
|
+
outputRoot: this.getOutputRoot(),
|
|
51165
|
+
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
51166
|
+
relativeFilePath: `${slug}-kiro.md`,
|
|
51167
|
+
frontmatter: {
|
|
51168
|
+
targets: ["kiro"],
|
|
51169
|
+
root: false,
|
|
51170
|
+
globs: [`${subprojectPath}/**/*`],
|
|
51171
|
+
agentsmd: { subprojectPath }
|
|
51172
|
+
},
|
|
51173
|
+
body: this.getFileContent(),
|
|
51174
|
+
validate: true
|
|
51175
|
+
});
|
|
51176
|
+
}
|
|
50549
51177
|
const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getRelativeFilePath());
|
|
50550
51178
|
const inclusion = typeof frontmatter.inclusion === "string" ? frontmatter.inclusion : void 0;
|
|
50551
51179
|
if (!inclusion) return this.toRulesyncRuleDefault();
|
|
@@ -50578,6 +51206,26 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
50578
51206
|
error: null
|
|
50579
51207
|
};
|
|
50580
51208
|
}
|
|
51209
|
+
/**
|
|
51210
|
+
* Nested `AGENTS.md` files anywhere in the workspace tree, which Kiro loads as
|
|
51211
|
+
* steering context alongside `.kiro/steering/`. Import-only, like every other
|
|
51212
|
+
* nested scan: these are hand-authored files outside a rulesync-owned
|
|
51213
|
+
* directory, so enumerating them for `--delete` would sweep away work rulesync
|
|
51214
|
+
* never wrote.
|
|
51215
|
+
* @see https://kiro.dev/docs/steering/
|
|
51216
|
+
*/
|
|
51217
|
+
static getNestedFilePatterns({ outputRoot }) {
|
|
51218
|
+
const root = toPosixPath(outputRoot);
|
|
51219
|
+
return {
|
|
51220
|
+
include: [`${root}/**/${KIRO_NESTED_STEERING_FILE_NAME}`],
|
|
51221
|
+
ignore: [
|
|
51222
|
+
`${root}/${KIRO_NESTED_STEERING_FILE_NAME}`,
|
|
51223
|
+
`${root}/**/.*/**`,
|
|
51224
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
51225
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
51226
|
+
]
|
|
51227
|
+
};
|
|
51228
|
+
}
|
|
50581
51229
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
50582
51230
|
return new KiroRule({
|
|
50583
51231
|
outputRoot,
|
|
@@ -52046,6 +52694,18 @@ const resolveIncludeLocalRoot = (options) => {
|
|
|
52046
52694
|
return parsed.data.includeLocalRoot ?? true;
|
|
52047
52695
|
};
|
|
52048
52696
|
/**
|
|
52697
|
+
* Whether `candidate` is `base` itself or a class that extends it.
|
|
52698
|
+
*
|
|
52699
|
+
* Class-identity dispatch (`factory.class === Base`) is subclass-blind: a target
|
|
52700
|
+
* whose adapter merely narrows another one — `ZoocodeRule extends RooRule` — fails
|
|
52701
|
+
* a strict `===` and silently falls through. That is how `zoocode` ended up
|
|
52702
|
+
* declaring `localRootMode: "separate-local-file"` while never emitting
|
|
52703
|
+
* `AGENTS.local.md`, in both directions and without a warning.
|
|
52704
|
+
*/
|
|
52705
|
+
function isClassOrSubclassOf({ candidate, base }) {
|
|
52706
|
+
return candidate === base || Object.prototype.isPrototypeOf.call(base, candidate);
|
|
52707
|
+
}
|
|
52708
|
+
/**
|
|
52049
52709
|
* Factory Map mapping tool targets to their rule factories.
|
|
52050
52710
|
* Using Map to preserve insertion order for consistent iteration.
|
|
52051
52711
|
*/
|
|
@@ -52399,7 +53059,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
52399
53059
|
meta: {
|
|
52400
53060
|
extension: "md",
|
|
52401
53061
|
supportsGlobal: true,
|
|
52402
|
-
ruleDiscoveryMode: "auto"
|
|
53062
|
+
ruleDiscoveryMode: "auto",
|
|
53063
|
+
localRootMode: "separate-local-file",
|
|
53064
|
+
localRootFileName: "AGENTS.local.md"
|
|
52403
53065
|
}
|
|
52404
53066
|
}],
|
|
52405
53067
|
["zed", {
|
|
@@ -52686,7 +53348,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52686
53348
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
52687
53349
|
}
|
|
52688
53350
|
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
52689
|
-
if (
|
|
53351
|
+
if (isClassOrSubclassOf({
|
|
53352
|
+
candidate: factory.class,
|
|
53353
|
+
base: ClaudecodeRule
|
|
53354
|
+
})) {
|
|
52690
53355
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
52691
53356
|
return new ClaudecodeRule({
|
|
52692
53357
|
outputRoot: this.outputRoot,
|
|
@@ -52699,7 +53364,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52699
53364
|
localRoot
|
|
52700
53365
|
});
|
|
52701
53366
|
}
|
|
52702
|
-
if (
|
|
53367
|
+
if (isClassOrSubclassOf({
|
|
53368
|
+
candidate: factory.class,
|
|
53369
|
+
base: ClaudecodeLegacyRule
|
|
53370
|
+
})) {
|
|
52703
53371
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
52704
53372
|
return new ClaudecodeLegacyRule({
|
|
52705
53373
|
outputRoot: this.outputRoot,
|
|
@@ -52711,7 +53379,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52711
53379
|
localRoot
|
|
52712
53380
|
});
|
|
52713
53381
|
}
|
|
52714
|
-
if (
|
|
53382
|
+
if (isClassOrSubclassOf({
|
|
53383
|
+
candidate: factory.class,
|
|
53384
|
+
base: RovodevRule
|
|
53385
|
+
})) return new RovodevRule({
|
|
52715
53386
|
outputRoot: this.outputRoot,
|
|
52716
53387
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52717
53388
|
relativeFilePath: fileName,
|
|
@@ -52720,7 +53391,10 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52720
53391
|
root: true,
|
|
52721
53392
|
localRoot
|
|
52722
53393
|
});
|
|
52723
|
-
if (
|
|
53394
|
+
if (isClassOrSubclassOf({
|
|
53395
|
+
candidate: factory.class,
|
|
53396
|
+
base: RooRule
|
|
53397
|
+
})) return new RooRule({
|
|
52724
53398
|
outputRoot: this.outputRoot,
|
|
52725
53399
|
relativeDirPath: relativeDirPath ?? ".",
|
|
52726
53400
|
relativeFilePath: fileName,
|
|
@@ -52729,7 +53403,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52729
53403
|
root: true,
|
|
52730
53404
|
localRoot
|
|
52731
53405
|
});
|
|
52732
|
-
if (
|
|
53406
|
+
if (isClassOrSubclassOf({
|
|
53407
|
+
candidate: factory.class,
|
|
53408
|
+
base: DevinRule
|
|
53409
|
+
})) return new DevinRule({
|
|
53410
|
+
outputRoot: this.outputRoot,
|
|
53411
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
53412
|
+
relativeFilePath: fileName,
|
|
53413
|
+
frontmatter: {},
|
|
53414
|
+
body,
|
|
53415
|
+
validate: true,
|
|
53416
|
+
root: true,
|
|
53417
|
+
localRoot
|
|
53418
|
+
});
|
|
53419
|
+
if (isClassOrSubclassOf({
|
|
53420
|
+
candidate: factory.class,
|
|
53421
|
+
base: QwencodeRule
|
|
53422
|
+
})) return new QwencodeRule({
|
|
52733
53423
|
outputRoot: this.outputRoot,
|
|
52734
53424
|
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
52735
53425
|
relativeFilePath: fileName,
|