rulesync 16.35.0 → 16.37.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/dist/cli/index.cjs +14 -4
- package/dist/cli/index.js +14 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-DxRza3o_.js → import-BnF2J75x.js} +1095 -211
- package/dist/import-BnF2J75x.js.map +1 -0
- package/dist/{import-j_cW_K5C.cjs → import-xo5me5dm.cjs} +1094 -210
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-DxRza3o_.js.map +0 -1
|
@@ -4078,24 +4078,60 @@ const CONTROL_CHARS = [
|
|
|
4078
4078
|
const hasControlChars = (val) => CONTROL_CHARS.some((char) => val.includes(char));
|
|
4079
4079
|
const safeString = zod_mini.z.pipe(zod_mini.z.string(), zod_mini.z.custom((val) => typeof val === "string" && !hasControlChars(val), "must not contain newline, carriage return, or NUL characters"));
|
|
4080
4080
|
/**
|
|
4081
|
+
* All canonical hook types — the union of the `type` values accepted by every
|
|
4082
|
+
* supported tool:
|
|
4083
|
+
* - `command` — universal (Claude Code, Codex CLI, Qwen Code, Cursor, …)
|
|
4084
|
+
* - `prompt` — Claude Code, Codex CLI, Qwen Code, Copilot CLI, Cursor, Devin
|
|
4085
|
+
* - `http` — Claude Code, Qwen Code, Copilot CLI, Grok CLI
|
|
4086
|
+
* - `agent` — Claude Code, Codex CLI
|
|
4087
|
+
* - `mcp_tool` — Claude Code
|
|
4088
|
+
* - `function` — Qwen Code (internal Skill-system executor)
|
|
4089
|
+
*/
|
|
4090
|
+
const HOOK_TYPES = [
|
|
4091
|
+
"command",
|
|
4092
|
+
"prompt",
|
|
4093
|
+
"http",
|
|
4094
|
+
"agent",
|
|
4095
|
+
"mcp_tool",
|
|
4096
|
+
"function"
|
|
4097
|
+
];
|
|
4098
|
+
/**
|
|
4099
|
+
* One choice offered by a Kiro `confirm` prompt: `run` decides whether picking
|
|
4100
|
+
* it executes the hook command. The prompt text is written back into JSON,
|
|
4101
|
+
* never embedded in generated code, so it is a plain string.
|
|
4102
|
+
*/
|
|
4103
|
+
const HookConfirmOptionSchema = zod_mini.z.looseObject({
|
|
4104
|
+
id: zod_mini.z.string(),
|
|
4105
|
+
label: zod_mini.z.string(),
|
|
4106
|
+
run: zod_mini.z.boolean()
|
|
4107
|
+
});
|
|
4108
|
+
/**
|
|
4109
|
+
* Kiro's confirmation prompt shown before a `Stop`-trigger command hook runs.
|
|
4110
|
+
* `confirmCommand` (optional, inside the block) names a command whose JSON
|
|
4111
|
+
* stdout drives the prompt at run time: `{ "skip": true }` suppresses it, a
|
|
4112
|
+
* replacement `question` / `options` object overrides the static prompt, and
|
|
4113
|
+
* on failure or timeout the static prompt is used. Stored verbatim: Kiro is
|
|
4114
|
+
* the only consumer, and it owns the shape.
|
|
4115
|
+
* https://kiro.dev/docs/hooks/
|
|
4116
|
+
*/
|
|
4117
|
+
const HookConfirmSchema = zod_mini.z.looseObject({
|
|
4118
|
+
question: zod_mini.z.string(),
|
|
4119
|
+
options: zod_mini.z.array(HookConfirmOptionSchema),
|
|
4120
|
+
confirmCommand: zod_mini.z.optional(safeString)
|
|
4121
|
+
});
|
|
4122
|
+
/**
|
|
4081
4123
|
* Canonical hook definition.
|
|
4082
4124
|
* Used in .rulesync/hooks.jsonc and mapped to tool-specific formats.
|
|
4083
4125
|
*/
|
|
4084
4126
|
const HookDefinitionSchema = zod_mini.z.looseObject({
|
|
4085
4127
|
command: zod_mini.z.optional(safeString),
|
|
4086
|
-
type: zod_mini.z.optional(zod_mini.z.enum(
|
|
4087
|
-
"command",
|
|
4088
|
-
"prompt",
|
|
4089
|
-
"http",
|
|
4090
|
-
"agent",
|
|
4091
|
-
"mcp_tool",
|
|
4092
|
-
"function"
|
|
4093
|
-
])),
|
|
4128
|
+
type: zod_mini.z.optional(zod_mini.z.enum(HOOK_TYPES)),
|
|
4094
4129
|
url: zod_mini.z.optional(safeString),
|
|
4095
4130
|
timeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
4096
4131
|
cacheTtl: zod_mini.z.optional(zod_mini.z.number().check((0, zod_mini.nonnegative)())),
|
|
4097
4132
|
matcher: zod_mini.z.optional(safeString),
|
|
4098
4133
|
enabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4134
|
+
confirm: zod_mini.z.optional(HookConfirmSchema),
|
|
4099
4135
|
prompt: zod_mini.z.optional(safeString),
|
|
4100
4136
|
loop_limit: zod_mini.z.optional(zod_mini.z.nullable(zod_mini.z.number())),
|
|
4101
4137
|
name: zod_mini.z.optional(safeString),
|
|
@@ -4373,9 +4409,13 @@ const AMP_HOOK_EVENTS = [
|
|
|
4373
4409
|
* the platform the other one owns — see `generateClineHookScript` and
|
|
4374
4410
|
* `generateClineHookPowerShellScript`.
|
|
4375
4411
|
*
|
|
4376
|
-
* `
|
|
4377
|
-
*
|
|
4378
|
-
*
|
|
4412
|
+
* `TaskCancel` is the shape of canonical `stopCancelled` (as Grok CLI's
|
|
4413
|
+
* `StopCancelled` and Codex CLI's `Interrupt` are): both runtimes fire it
|
|
4414
|
+
* *instead of* `TaskComplete` when the run ends aborted — the VS Code
|
|
4415
|
+
* extension from `afterRun` when the result status is `aborted`, the SDK/CLI
|
|
4416
|
+
* as `agent_abort` — and neither lets the script cancel anything. `TaskResume`
|
|
4417
|
+
* has no canonical counterpart and stays unmapped rather than being
|
|
4418
|
+
* approximated by `sessionStart`, whose semantics differ.
|
|
4379
4419
|
*
|
|
4380
4420
|
* @see https://github.com/cline/cline/blob/main/apps/vscode/src/core/hooks/utils.ts
|
|
4381
4421
|
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-config.ts
|
|
@@ -4389,7 +4429,8 @@ const CLINE_HOOK_EVENTS = [
|
|
|
4389
4429
|
"preCompact",
|
|
4390
4430
|
"notification",
|
|
4391
4431
|
"taskCompleted",
|
|
4392
|
-
"afterError"
|
|
4432
|
+
"afterError",
|
|
4433
|
+
"stopCancelled"
|
|
4393
4434
|
];
|
|
4394
4435
|
/**
|
|
4395
4436
|
* Hook events supported by GitHub Copilot (cloud coding agent).
|
|
@@ -4707,23 +4748,32 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
4707
4748
|
* Hook events supported by Reasonix.
|
|
4708
4749
|
*
|
|
4709
4750
|
* Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
|
|
4710
|
-
* (global)
|
|
4711
|
-
* `
|
|
4712
|
-
* `
|
|
4751
|
+
* (global) accepts the thirteen events declared in `internal/hook/hook.go`
|
|
4752
|
+
* (`var Events`): `PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
|
|
4753
|
+
* `PermissionRequest`, `UserPromptSubmit`, `Stop`, `StopFailure`,
|
|
4754
|
+
* `PostLLMCall`, `SessionStart`, `SessionEnd`, `SubagentStop`, `Notification`,
|
|
4755
|
+
* `PreCompact`. The zh-CN desktop hooks page documents only ten of them, so the
|
|
4756
|
+
* source file is the authoritative list. All thirteen have a clean canonical
|
|
4713
4757
|
* equivalent and are mapped: `PreToolUse`, `PostToolUse`,
|
|
4714
|
-
* `
|
|
4715
|
-
* `
|
|
4716
|
-
* `
|
|
4717
|
-
* `
|
|
4718
|
-
* `
|
|
4719
|
-
*
|
|
4758
|
+
* `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit` ←
|
|
4759
|
+
* `beforeSubmitPrompt`, `Stop`, `StopFailure`, `SessionStart`, `SessionEnd`,
|
|
4760
|
+
* `SubagentStop`, `PostLLMCall` ← `postModelInvocation`, `Notification` ←
|
|
4761
|
+
* `notification`, and `PreCompact` ← `preCompact`.
|
|
4762
|
+
* `match` (Reasonix's matcher field name) is honored on the four tool-scoped
|
|
4763
|
+
* events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
|
|
4764
|
+
* `PermissionRequest` — `UsesToolMatcher` in `internal/hook/inspect.go`) and
|
|
4765
|
+
* ignored everywhere else.
|
|
4766
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/hook/hook.go
|
|
4720
4767
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
4721
4768
|
*/
|
|
4722
4769
|
const REASONIX_HOOK_EVENTS = [
|
|
4723
4770
|
"preToolUse",
|
|
4724
4771
|
"postToolUse",
|
|
4772
|
+
"postToolUseFailure",
|
|
4773
|
+
"permissionRequest",
|
|
4725
4774
|
"beforeSubmitPrompt",
|
|
4726
4775
|
"stop",
|
|
4776
|
+
"stopFailure",
|
|
4727
4777
|
"sessionStart",
|
|
4728
4778
|
"sessionEnd",
|
|
4729
4779
|
"subagentStop",
|
|
@@ -4769,8 +4819,8 @@ const GROKCLI_HOOK_EVENTS = [
|
|
|
4769
4819
|
* added in 0.32.0 (`TurnStarted`, `UserPromptQueued`, `TaskStarted`,
|
|
4770
4820
|
* `SessionHeartbeat`), none of which is mapped onto a canonical rulesync event
|
|
4771
4821
|
* here. `Interrupt` (fires instead of `Stop` when the user interrupts a turn)
|
|
4772
|
-
* does have a canonical shape — `stopCancelled`, which Grok CLI
|
|
4773
|
-
* map — but Kimi keeps it native-only for now, since an existing `kimi-code`
|
|
4822
|
+
* does have a canonical shape — `stopCancelled`, which Grok CLI, Codex CLI and
|
|
4823
|
+
* Cline map — but Kimi keeps it native-only for now, since an existing `kimi-code`
|
|
4774
4824
|
* override addressing it by name would otherwise double up with a canonical
|
|
4775
4825
|
* `stopCancelled` block; folding it in is a follow-up. All six are listed in
|
|
4776
4826
|
* `KIMI_CODE_NATIVE_HOOK_EVENTS` so a per-tool `kimi-code` override can
|
|
@@ -5065,8 +5115,8 @@ const CONTINUE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
5065
5115
|
/**
|
|
5066
5116
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
5067
5117
|
*
|
|
5068
|
-
* Hermes validates hook events against a fixed `VALID_HOOKS` set —
|
|
5069
|
-
* of v0.
|
|
5118
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 39 entries as
|
|
5119
|
+
* of v0.21.3 (`v2026.9.14`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
5070
5120
|
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
5071
5121
|
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
5072
5122
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
@@ -5090,11 +5140,13 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
5090
5140
|
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
5091
5141
|
* Shell hooks are gated on the same set.
|
|
5092
5142
|
*
|
|
5093
|
-
* Verified against the tag `v2026.
|
|
5094
|
-
* entries, grown from 23 at `v2026.8.3` (v0.20.0)
|
|
5095
|
-
*
|
|
5143
|
+
* Verified against the tag `v2026.9.14` (v0.21.3): `VALID_HOOKS` holds 39
|
|
5144
|
+
* entries, grown from 23 at `v2026.8.3` (v0.20.0) and 37 at `v2026.8.16`
|
|
5145
|
+
* (v0.20.2) — v0.21 added `agent_loop_stopped` and `on_room_member_activity`.
|
|
5146
|
+
* This list holds 38 of them — see
|
|
5147
|
+
* {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
5096
5148
|
*
|
|
5097
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.
|
|
5149
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/hermes_cli/plugins.py
|
|
5098
5150
|
*/
|
|
5099
5151
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
5100
5152
|
"pre_tool_call",
|
|
@@ -5120,8 +5172,10 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
5120
5172
|
"subagent_start",
|
|
5121
5173
|
"subagent_stop",
|
|
5122
5174
|
"pre_gateway_dispatch",
|
|
5175
|
+
"agent_loop_stopped",
|
|
5123
5176
|
"pre_approval_request",
|
|
5124
5177
|
"post_approval_response",
|
|
5178
|
+
"on_room_member_activity",
|
|
5125
5179
|
"pre_transcription",
|
|
5126
5180
|
"kanban_task_claimed",
|
|
5127
5181
|
"kanban_task_completed",
|
|
@@ -5144,8 +5198,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
5144
5198
|
* rulesync only ever writes shell hooks, so these are excluded from
|
|
5145
5199
|
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
5146
5200
|
*
|
|
5147
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.
|
|
5148
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.
|
|
5201
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
5202
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/agent/shell_hooks.py — `_parse_hooks_block`
|
|
5149
5203
|
*/
|
|
5150
5204
|
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
5151
5205
|
/**
|
|
@@ -5446,7 +5500,8 @@ const CANONICAL_TO_CLINE_EVENT_NAMES = {
|
|
|
5446
5500
|
preCompact: "PreCompact",
|
|
5447
5501
|
notification: "Notification",
|
|
5448
5502
|
taskCompleted: "TaskComplete",
|
|
5449
|
-
afterError: "TaskError"
|
|
5503
|
+
afterError: "TaskError",
|
|
5504
|
+
stopCancelled: "TaskCancel"
|
|
5450
5505
|
};
|
|
5451
5506
|
/**
|
|
5452
5507
|
* Map canonical camelCase event names to Copilot camelCase.
|
|
@@ -5764,8 +5819,11 @@ const QWENCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
5764
5819
|
const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
5765
5820
|
preToolUse: "PreToolUse",
|
|
5766
5821
|
postToolUse: "PostToolUse",
|
|
5822
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
5823
|
+
permissionRequest: "PermissionRequest",
|
|
5767
5824
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
5768
5825
|
stop: "Stop",
|
|
5826
|
+
stopFailure: "StopFailure",
|
|
5769
5827
|
sessionStart: "SessionStart",
|
|
5770
5828
|
sessionEnd: "SessionEnd",
|
|
5771
5829
|
subagentStop: "SubagentStop",
|
|
@@ -7656,11 +7714,15 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
7656
7714
|
});
|
|
7657
7715
|
/**
|
|
7658
7716
|
* Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
|
|
7659
|
-
* `.codex/config.toml`. `on-failure` is a
|
|
7660
|
-
* Codex still
|
|
7661
|
-
*
|
|
7662
|
-
*
|
|
7717
|
+
* `.codex/config.toml`. `on-failure` is a deprecated alias for `on-request`
|
|
7718
|
+
* that Codex still reads, so it stays accepted (generate warns) so existing
|
|
7719
|
+
* configs round-trip. `untrusted` was retired in Codex 0.149.0 and makes
|
|
7720
|
+
* Codex refuse to start; it stays in the enum only so an existing
|
|
7721
|
+
* `.rulesync/permissions.jsonc` still parses — generate warns and does not
|
|
7722
|
+
* write it, and import does not lift it. The granular table form
|
|
7723
|
+
* (`{ granular = { … } }`) is modeled separately in the override union.
|
|
7663
7724
|
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
7725
|
+
* @see https://github.com/openai/codex/pull/39630
|
|
7664
7726
|
*/
|
|
7665
7727
|
const CodexApprovalPolicySchema = zod_mini.z.enum([
|
|
7666
7728
|
"untrusted",
|
|
@@ -7730,11 +7792,12 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
|
|
|
7730
7792
|
* selected directly via `default_permissions` and skips the managed profile
|
|
7731
7793
|
* entirely — canonical filesystem/network rules are ignored in that mode.
|
|
7732
7794
|
* Defaults to `:workspace` when unspecified.
|
|
7733
|
-
* - `approval_policy` — `
|
|
7795
|
+
* - `approval_policy` — `on-request` (deprecated alias `on-failure`) |
|
|
7734
7796
|
* `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
|
|
7735
7797
|
* schema has required fields that are brittle to model as typed keys).
|
|
7736
7798
|
* Defaults to `on-request` when neither the override nor the existing
|
|
7737
|
-
* config sets it.
|
|
7799
|
+
* config sets it. `untrusted` (retired in Codex 0.149.0) is still parsed
|
|
7800
|
+
* but skipped with a warning on generate, since Codex refuses to start on it.
|
|
7738
7801
|
* - `sandbox_mode` — **deprecated.** `read-only` | `workspace-write` |
|
|
7739
7802
|
* `danger-full-access`, with the sibling `sandbox_workspace_write` table
|
|
7740
7803
|
* (`network_access`, `writable_roots`, …). Codex has superseded the classic
|
|
@@ -8382,6 +8445,7 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
8382
8445
|
trigger: zod_mini.z.optional(zod_mini.z.string()),
|
|
8383
8446
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8384
8447
|
})),
|
|
8448
|
+
aiassistant: zod_mini.z.optional(zod_mini.z.looseObject({ apply: zod_mini.z.optional(zod_mini.z.string()) })),
|
|
8385
8449
|
devin: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
8386
8450
|
trigger: zod_mini.z.optional(zod_mini.z.string()),
|
|
8387
8451
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -9606,6 +9670,8 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
9606
9670
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
9607
9671
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9608
9672
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9673
|
+
icon: zod_mini.z.optional(zod_mini.z.string()),
|
|
9674
|
+
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
9609
9675
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
9610
9676
|
})),
|
|
9611
9677
|
factorydroid: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -9620,7 +9686,8 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
9620
9686
|
})),
|
|
9621
9687
|
grokcli: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9622
9688
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9623
|
-
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean())
|
|
9689
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9690
|
+
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
9624
9691
|
})),
|
|
9625
9692
|
dsh: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9626
9693
|
whenToUse: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -9847,8 +9914,32 @@ var RulesyncSubagent = class RulesyncSubagent extends RulesyncFile {
|
|
|
9847
9914
|
});
|
|
9848
9915
|
}
|
|
9849
9916
|
};
|
|
9850
|
-
|
|
9851
|
-
|
|
9917
|
+
const SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
|
9918
|
+
const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9919
|
+
/**
|
|
9920
|
+
* Check a skill `name` against the length and character rules, phrased for the
|
|
9921
|
+
* `authority` that enforces them (e.g. "the Agent Skills spec" or "Zed"). When
|
|
9922
|
+
* that authority does something with a bad name beyond flagging it — Zed
|
|
9923
|
+
* refuses to load the skill — `consequence` names it so the warning says what
|
|
9924
|
+
* the author is about to lose. An empty name is the caller's concern: what it
|
|
9925
|
+
* means differs per consumer.
|
|
9926
|
+
*/
|
|
9927
|
+
function collectSkillNameViolations({ name, authority, consequence }) {
|
|
9928
|
+
const violations = [];
|
|
9929
|
+
if (name.length > 64) violations.push(`\`name\` is ${name.length} characters; ${authority} allows at most 64${consequence === void 0 ? "" : ` and ${consequence}`}`);
|
|
9930
|
+
if (!SKILL_NAME_PATTERN.test(name)) violations.push(`\`name\` ${quoteForLog(name)} must contain only lowercase letters, digits and single hyphens, with no leading, trailing or consecutive hyphens${consequence === void 0 ? "" : `; ${authority} ${consequence}`}`);
|
|
9931
|
+
return violations;
|
|
9932
|
+
}
|
|
9933
|
+
/**
|
|
9934
|
+
* Report each violation as a warning prefixed with the skill file it concerns.
|
|
9935
|
+
* The path is shown POSIX-style and stripped of control characters so a hostile
|
|
9936
|
+
* directory name cannot forge log lines; `warnWithFallback` keeps the report
|
|
9937
|
+
* visible when the caller has no logger to hand.
|
|
9938
|
+
*/
|
|
9939
|
+
function warnSkillViolations({ skillPath, violations, logger }) {
|
|
9940
|
+
const shownPath = stripControlCharacters(toPosixPath(skillPath));
|
|
9941
|
+
for (const violation of violations) warnWithFallback(logger, `${shownPath}: ${violation}`);
|
|
9942
|
+
}
|
|
9852
9943
|
/**
|
|
9853
9944
|
* Whether a directory on disk can be addressed by its name.
|
|
9854
9945
|
*
|
|
@@ -10672,10 +10763,13 @@ const AmpCheckFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
10672
10763
|
/**
|
|
10673
10764
|
* Represents an Amp code review check.
|
|
10674
10765
|
*
|
|
10675
|
-
* Amp
|
|
10676
|
-
* scoped to the project (`.agents/checks/`) and user-wide (`~/.config/amp/checks
|
|
10677
|
-
*
|
|
10678
|
-
*
|
|
10766
|
+
* Amp reads code review checks as Markdown files with YAML frontmatter,
|
|
10767
|
+
* scoped to the project (`.agents/checks/`) and user-wide (`~/.config/amp/checks/`;
|
|
10768
|
+
* Amp also reads `~/.config/agents/checks/`, which rulesync does not write).
|
|
10769
|
+
* Since the docs rebuild the supported runner is the official
|
|
10770
|
+
* `@amp/review-checks` plugin (`amp review` is deprecated); existing checks in
|
|
10771
|
+
* this format continue to apply.
|
|
10772
|
+
* @see https://ampcode.com/docs/review
|
|
10679
10773
|
*/
|
|
10680
10774
|
var AmpCheck = class AmpCheck extends ToolCheck {
|
|
10681
10775
|
frontmatter;
|
|
@@ -16915,10 +17009,8 @@ const AgentsSkillsSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
16915
17009
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16916
17010
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
16917
17011
|
});
|
|
16918
|
-
const NAME_MAX_LENGTH = 64;
|
|
16919
|
-
const DESCRIPTION_MAX_LENGTH = 1024;
|
|
16920
17012
|
const COMPATIBILITY_MAX_LENGTH = 500;
|
|
16921
|
-
const
|
|
17013
|
+
const AGENT_SKILLS_SPEC = "the Agent Skills spec";
|
|
16922
17014
|
/**
|
|
16923
17015
|
* Placeholder for an object that has already been encoded once in the same
|
|
16924
17016
|
* value. YAML anchors let one document reference the same node repeatedly, and
|
|
@@ -17082,12 +17174,14 @@ function collectAgentSkillViolations({ frontmatter, dirName, sourceAllowedTools
|
|
|
17082
17174
|
const { name, description } = frontmatter;
|
|
17083
17175
|
if (name.length === 0) violations.push("`name` is required and must not be empty");
|
|
17084
17176
|
else {
|
|
17085
|
-
|
|
17086
|
-
|
|
17177
|
+
violations.push(...collectSkillNameViolations({
|
|
17178
|
+
name,
|
|
17179
|
+
authority: AGENT_SKILLS_SPEC
|
|
17180
|
+
}));
|
|
17087
17181
|
if (name !== dirName) violations.push(`\`name\` "${name}" must match its parent directory name "${dirName}"; conformant clients require them to be equal`);
|
|
17088
17182
|
}
|
|
17089
17183
|
if (description.length === 0) violations.push(EMPTY_SKILL_DESCRIPTION_VIOLATION);
|
|
17090
|
-
else if (description.length >
|
|
17184
|
+
else if (description.length > 1024) violations.push(`\`description\` is ${description.length} characters; ${AGENT_SKILLS_SPEC} allows at most ${SKILL_DESCRIPTION_MAX_LENGTH}`);
|
|
17091
17185
|
const { compatibility } = frontmatter;
|
|
17092
17186
|
if (typeof compatibility !== "string" && compatibility !== void 0) violations.push("`compatibility` must be a string; the Agent Skills spec does not allow a mapping here");
|
|
17093
17187
|
else if (compatibility !== void 0 && compatibility.length > COMPATIBILITY_MAX_LENGTH) violations.push(`\`compatibility\` is ${compatibility.length} characters; the Agent Skills spec allows at most ${COMPATIBILITY_MAX_LENGTH}`);
|
|
@@ -17205,16 +17299,19 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
|
|
|
17205
17299
|
* directory rather than a same-named project path.
|
|
17206
17300
|
*/
|
|
17207
17301
|
static reportSpecViolations({ outputRoot, relativeDirPath, dirName, frontmatter, sourceAllowedTools, logger }) {
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
|
|
17302
|
+
warnSkillViolations({
|
|
17303
|
+
skillPath: agentSkillFilePath({
|
|
17304
|
+
outputRoot,
|
|
17305
|
+
relativeDirPath,
|
|
17306
|
+
dirName
|
|
17307
|
+
}),
|
|
17308
|
+
violations: collectAgentSkillViolations({
|
|
17309
|
+
frontmatter,
|
|
17310
|
+
dirName,
|
|
17311
|
+
sourceAllowedTools
|
|
17312
|
+
}),
|
|
17313
|
+
logger
|
|
17212
17314
|
});
|
|
17213
|
-
for (const violation of collectAgentSkillViolations({
|
|
17214
|
-
frontmatter,
|
|
17215
|
-
dirName,
|
|
17216
|
-
sourceAllowedTools
|
|
17217
|
-
})) warnWithFallback(logger, `${stripControlCharacters(toPosixPath(skillPath))}: ${violation}`);
|
|
17218
17315
|
}
|
|
17219
17316
|
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
17220
17317
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
@@ -17854,6 +17951,15 @@ const KiloCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
17854
17951
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
17855
17952
|
variant: zod_mini.z.optional(zod_mini.z.string())
|
|
17856
17953
|
});
|
|
17954
|
+
/**
|
|
17955
|
+
* Command names Kilo keeps for itself. Its loader throws on a custom command
|
|
17956
|
+
* file named `goal.md` in either the project or the global directory ("The
|
|
17957
|
+
* /goal command is reserved for session goals. Rename the custom command."),
|
|
17958
|
+
* and a throw there aborts the whole command scan — every other custom
|
|
17959
|
+
* command becomes unreachable too, not only the reserved one.
|
|
17960
|
+
* https://github.com/Kilo-Org/kilocode/blob/v7.6.2/packages/opencode/src/command/index.ts
|
|
17961
|
+
*/
|
|
17962
|
+
const KILO_RESERVED_COMMAND_NAMES = /* @__PURE__ */ new Set(["goal"]);
|
|
17857
17963
|
var KiloCommand = class KiloCommand extends ToolCommand {
|
|
17858
17964
|
frontmatter;
|
|
17859
17965
|
body;
|
|
@@ -17896,6 +18002,20 @@ var KiloCommand = class KiloCommand extends ToolCommand {
|
|
|
17896
18002
|
validate: true
|
|
17897
18003
|
});
|
|
17898
18004
|
}
|
|
18005
|
+
/**
|
|
18006
|
+
* Refuse to write a command Kilo reserves (see
|
|
18007
|
+
* {@link KILO_RESERVED_COMMAND_NAMES}). The name Kilo loads is the file
|
|
18008
|
+
* stem, and the processor calls this on the flattened path, so `goal.md`
|
|
18009
|
+
* is refused at any nesting depth under the basename naming and only at the
|
|
18010
|
+
* top level under the path naming, where `git/goal.md` becomes `git-goal`.
|
|
18011
|
+
* Import is untouched: a `goal.md` already in `.kilo/commands/` is read as
|
|
18012
|
+
* it is, since the reservation is Kilo's to enforce on its own files.
|
|
18013
|
+
*/
|
|
18014
|
+
static getWriteBlockReason({ rulesyncCommand }) {
|
|
18015
|
+
const stem = (0, node_path.basename)(rulesyncCommand.getRelativeFilePath(), ".md");
|
|
18016
|
+
if (!KILO_RESERVED_COMMAND_NAMES.has(stem)) return null;
|
|
18017
|
+
return `Kilo reserves the /${stem} command for session goals and refuses to load a custom command by that name — it would abort Kilo's whole command scan, making every other custom command unreachable too. Rename the command, or exclude it from this target with \`targets\`.`;
|
|
18018
|
+
}
|
|
17899
18019
|
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
17900
18020
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
17901
18021
|
const kiloFields = rulesyncFrontmatter.kilo ?? {};
|
|
@@ -20118,6 +20238,14 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
20118
20238
|
if (!factory.class.isTargetedByRulesyncCommand(rulesyncCommand)) return null;
|
|
20119
20239
|
const originalRelativePath = rulesyncCommand.getRelativeFilePath();
|
|
20120
20240
|
const commandToConvert = factory.meta.supportsSubdirectory ? rulesyncCommand : this.flattenRelativeFilePath(rulesyncCommand);
|
|
20241
|
+
const writeBlockReason = factory.class.getWriteBlockReason?.({
|
|
20242
|
+
rulesyncCommand: commandToConvert,
|
|
20243
|
+
global: this.global
|
|
20244
|
+
});
|
|
20245
|
+
if (writeBlockReason !== void 0 && writeBlockReason !== null) {
|
|
20246
|
+
this.logger.warn(`Skipping command ${quoteForLog(originalRelativePath)} for '${this.toolTarget}': ${writeBlockReason}`);
|
|
20247
|
+
return null;
|
|
20248
|
+
}
|
|
20121
20249
|
if (!factory.meta.supportsSubdirectory) {
|
|
20122
20250
|
const flattenedPath = commandToConvert.getRelativeFilePath();
|
|
20123
20251
|
const firstOrigin = flattenedPathOrigins.get(flattenedPath);
|
|
@@ -20987,6 +21115,72 @@ function isSupportedHookType({ type, converterConfig }) {
|
|
|
20987
21115
|
return converterConfig.supportedHookTypes?.has(type ?? "command") ?? true;
|
|
20988
21116
|
}
|
|
20989
21117
|
/**
|
|
21118
|
+
* Whether a keyword field applies to this hook, warning about the reason when
|
|
21119
|
+
* it does not. Shared by both directions: the field is unusable on the tool
|
|
21120
|
+
* side either way, so the value is dropped rather than carried into a config
|
|
21121
|
+
* the tool would ignore or refuse.
|
|
21122
|
+
*/
|
|
21123
|
+
function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }) {
|
|
21124
|
+
const { canonical, tool, commandOnly, events } = spec;
|
|
21125
|
+
const field = direction === "generated" ? canonical : tool;
|
|
21126
|
+
if (!isFieldApplicable({
|
|
21127
|
+
commandOnly,
|
|
21128
|
+
hookType
|
|
21129
|
+
})) {
|
|
21130
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not ${direction}.`);
|
|
21131
|
+
return false;
|
|
21132
|
+
}
|
|
21133
|
+
if (events !== void 0 && !events.has(eventName)) {
|
|
21134
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${[...events].map((event) => `"${event}"`).join(", ")} only, so it is not ${direction}.`);
|
|
21135
|
+
return false;
|
|
21136
|
+
}
|
|
21137
|
+
return true;
|
|
21138
|
+
}
|
|
21139
|
+
/**
|
|
21140
|
+
* Emit the keyword passthrough fields: a canonical `true` becomes the tool's
|
|
21141
|
+
* keyword, while `false` is omitted as the tool's own default. A `true` the
|
|
21142
|
+
* tool would not honour on this hook is warned about, like the other kinds;
|
|
21143
|
+
* a `false` there is dropped silently, since omitting it changes nothing.
|
|
21144
|
+
*/
|
|
21145
|
+
function emitKeywordFields({ def, hookType, eventName, fields, warn }) {
|
|
21146
|
+
const emitted = {};
|
|
21147
|
+
for (const spec of fields) {
|
|
21148
|
+
if (def[spec.canonical] !== true) continue;
|
|
21149
|
+
if (isKeywordFieldApplicable({
|
|
21150
|
+
spec,
|
|
21151
|
+
hookType,
|
|
21152
|
+
eventName,
|
|
21153
|
+
direction: "generated",
|
|
21154
|
+
warn
|
|
21155
|
+
})) emitted[spec.tool] = spec.trueValue;
|
|
21156
|
+
}
|
|
21157
|
+
return emitted;
|
|
21158
|
+
}
|
|
21159
|
+
/**
|
|
21160
|
+
* Import the keyword passthrough fields back into canonical booleans,
|
|
21161
|
+
* reversing {@link emitKeywordFields}. Only the two documented keywords are
|
|
21162
|
+
* read; anything else is warned about and dropped, so a typo in a hand-written
|
|
21163
|
+
* tool file never lands in `.rulesync/hooks.*` as a boolean it did not mean.
|
|
21164
|
+
*/
|
|
21165
|
+
function importKeywordFields({ h, hookType, eventName, fields, warn }) {
|
|
21166
|
+
const imported = {};
|
|
21167
|
+
for (const spec of fields) {
|
|
21168
|
+
const value = h[spec.tool];
|
|
21169
|
+
if (value === void 0) continue;
|
|
21170
|
+
if (!isKeywordFieldApplicable({
|
|
21171
|
+
spec,
|
|
21172
|
+
hookType,
|
|
21173
|
+
eventName,
|
|
21174
|
+
direction: "imported",
|
|
21175
|
+
warn
|
|
21176
|
+
})) continue;
|
|
21177
|
+
if (value === spec.trueValue) imported[spec.canonical] = true;
|
|
21178
|
+
else if (spec.falseValue !== void 0 && value === spec.falseValue) imported[spec.canonical] = false;
|
|
21179
|
+
else warn?.(`Dropping "${spec.tool}" from an imported "${hookType}" hook on "${eventName}": ${quoteValueForWarning(value)} is not a value this tool documents for it.`);
|
|
21180
|
+
}
|
|
21181
|
+
return imported;
|
|
21182
|
+
}
|
|
21183
|
+
/**
|
|
20990
21184
|
* Emit every per-hook passthrough kind for one canonical definition.
|
|
20991
21185
|
*/
|
|
20992
21186
|
function emitAllPassthroughFields({ def, hookType, eventName, converterConfig, warn }) {
|
|
@@ -20999,6 +21193,13 @@ function emitAllPassthroughFields({ def, hookType, eventName, converterConfig, w
|
|
|
20999
21193
|
isValid: isBooleanValue,
|
|
21000
21194
|
warn
|
|
21001
21195
|
}),
|
|
21196
|
+
...emitKeywordFields({
|
|
21197
|
+
def,
|
|
21198
|
+
hookType,
|
|
21199
|
+
eventName,
|
|
21200
|
+
fields: converterConfig.keywordPassthroughFields ?? [],
|
|
21201
|
+
warn
|
|
21202
|
+
}),
|
|
21002
21203
|
...emitPassthroughFields({
|
|
21003
21204
|
def,
|
|
21004
21205
|
hookType,
|
|
@@ -21327,7 +21528,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
21327
21528
|
* Import every per-hook passthrough kind for one tool hook record, reversing
|
|
21328
21529
|
* the emit side in {@link buildToolHooks}.
|
|
21329
21530
|
*/
|
|
21330
|
-
function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
21531
|
+
function importAllPassthroughFields({ h, hookType, eventName, converterConfig, warn }) {
|
|
21331
21532
|
return {
|
|
21332
21533
|
...importPassthroughFields({
|
|
21333
21534
|
h,
|
|
@@ -21336,6 +21537,13 @@ function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
|
21336
21537
|
isValid: isBooleanValue,
|
|
21337
21538
|
warn
|
|
21338
21539
|
}),
|
|
21540
|
+
...importKeywordFields({
|
|
21541
|
+
h,
|
|
21542
|
+
hookType,
|
|
21543
|
+
eventName,
|
|
21544
|
+
fields: converterConfig.keywordPassthroughFields ?? [],
|
|
21545
|
+
warn
|
|
21546
|
+
}),
|
|
21339
21547
|
...importPassthroughFields({
|
|
21340
21548
|
h,
|
|
21341
21549
|
hookType,
|
|
@@ -21381,7 +21589,7 @@ function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
|
21381
21589
|
/**
|
|
21382
21590
|
* Convert a single tool hook record into a canonical hook definition.
|
|
21383
21591
|
*/
|
|
21384
|
-
function toolHookToCanonical({ h, rawEntry, converterConfig, warn }) {
|
|
21592
|
+
function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn }) {
|
|
21385
21593
|
const hookType = resolveImportedHookType({
|
|
21386
21594
|
value: h.type,
|
|
21387
21595
|
converterConfig
|
|
@@ -21429,6 +21637,7 @@ function toolHookToCanonical({ h, rawEntry, converterConfig, warn }) {
|
|
|
21429
21637
|
...importAllPassthroughFields({
|
|
21430
21638
|
h,
|
|
21431
21639
|
hookType,
|
|
21640
|
+
eventName,
|
|
21432
21641
|
converterConfig,
|
|
21433
21642
|
warn
|
|
21434
21643
|
}),
|
|
@@ -21515,7 +21724,7 @@ function describeHookSkipReason({ h, rawEntry, hookType, converterConfig }) {
|
|
|
21515
21724
|
/**
|
|
21516
21725
|
* Convert a single tool matcher entry into canonical hook definitions.
|
|
21517
21726
|
*/
|
|
21518
|
-
function toolMatcherEntryToCanonical({ rawEntry, converterConfig, warn }) {
|
|
21727
|
+
function toolMatcherEntryToCanonical({ rawEntry, eventName, converterConfig, warn }) {
|
|
21519
21728
|
const hookDefs = rawEntry.hooks ?? [];
|
|
21520
21729
|
const groupSkipReason = describeGroupSkipReason({
|
|
21521
21730
|
rawEntry,
|
|
@@ -21543,6 +21752,7 @@ function toolMatcherEntryToCanonical({ rawEntry, converterConfig, warn }) {
|
|
|
21543
21752
|
definitions.push(toolHookToCanonical({
|
|
21544
21753
|
h,
|
|
21545
21754
|
rawEntry,
|
|
21755
|
+
eventName,
|
|
21546
21756
|
converterConfig,
|
|
21547
21757
|
warn
|
|
21548
21758
|
}));
|
|
@@ -21590,6 +21800,7 @@ function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
|
21590
21800
|
if (!isToolMatcherEntry(rawEntry)) continue;
|
|
21591
21801
|
defs.push(...toolMatcherEntryToCanonical({
|
|
21592
21802
|
rawEntry,
|
|
21803
|
+
eventName,
|
|
21593
21804
|
converterConfig,
|
|
21594
21805
|
warn
|
|
21595
21806
|
}));
|
|
@@ -23475,14 +23686,23 @@ var ContinueHooks = class extends SettingsJsonHooks {
|
|
|
23475
23686
|
* portable `command` field is written, which upstream copies to both when
|
|
23476
23687
|
* neither is present. Note the cloud agent runs hooks in a Linux sandbox and
|
|
23477
23688
|
* honors only `bash` and `command` — a `powershell` entry is ignored there.
|
|
23689
|
+
* VS Code reads the same file and maps `bash` to `osx` / `linux` and
|
|
23690
|
+
* `powershell` to `windows`; it also documents its own per-OS overrides
|
|
23691
|
+
* (`windows`, `linux`, `osx`) next to `command`, which are typed here so an
|
|
23692
|
+
* entry that uses them instead of a shell field is not mistaken for one
|
|
23693
|
+
* without a command.
|
|
23478
23694
|
*
|
|
23479
23695
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23696
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23480
23697
|
*/
|
|
23481
23698
|
const CopilotHookEntrySchema = zod_mini.z.looseObject({
|
|
23482
23699
|
type: zod_mini.z.string(),
|
|
23483
23700
|
bash: zod_mini.z.optional(zod_mini.z.string()),
|
|
23484
23701
|
powershell: zod_mini.z.optional(zod_mini.z.string()),
|
|
23485
23702
|
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
23703
|
+
windows: zod_mini.z.optional(zod_mini.z.string()),
|
|
23704
|
+
linux: zod_mini.z.optional(zod_mini.z.string()),
|
|
23705
|
+
osx: zod_mini.z.optional(zod_mini.z.string()),
|
|
23486
23706
|
cwd: zod_mini.z.optional(zod_mini.z.string()),
|
|
23487
23707
|
env: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
23488
23708
|
timeoutSec: zod_mini.z.optional(zod_mini.z.number()),
|
|
@@ -23573,17 +23793,30 @@ function resolveImportCommand$1(entry, logger) {
|
|
|
23573
23793
|
*
|
|
23574
23794
|
* Generate re-emits any non-canonical key verbatim through `rest`, so a key
|
|
23575
23795
|
* dropped here does not survive an import → generate round trip. `cwd` is a
|
|
23576
|
-
* documented Copilot hook field and was previously lost that way
|
|
23796
|
+
* documented Copilot hook field and was previously lost that way, as were the
|
|
23797
|
+
* VS Code per-OS overrides `windows` / `linux` / `osx`: an entry authored with
|
|
23798
|
+
* only those (no `command`) imported as a hook with no command at all and was
|
|
23799
|
+
* regenerated empty.
|
|
23577
23800
|
*
|
|
23578
23801
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23579
23802
|
* @see https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks
|
|
23803
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23580
23804
|
*/
|
|
23581
23805
|
function importPassthrough$1(entry) {
|
|
23582
23806
|
const passthrough = {};
|
|
23583
23807
|
if (entry.cwd !== void 0) passthrough.cwd = entry.cwd;
|
|
23584
23808
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
23809
|
+
for (const key of OS_OVERRIDE_KEYS) if (entry[key] !== void 0) passthrough[key] = entry[key];
|
|
23585
23810
|
return passthrough;
|
|
23586
23811
|
}
|
|
23812
|
+
const OS_OVERRIDE_KEYS = [
|
|
23813
|
+
"windows",
|
|
23814
|
+
"linux",
|
|
23815
|
+
"osx"
|
|
23816
|
+
];
|
|
23817
|
+
function hasOsOverride(passthrough) {
|
|
23818
|
+
return OS_OVERRIDE_KEYS.some((key) => passthrough[key] !== void 0);
|
|
23819
|
+
}
|
|
23587
23820
|
/**
|
|
23588
23821
|
* Extract hooks from Copilot hooks JSON into canonical format.
|
|
23589
23822
|
* Copilot format: { version: 1, hooks: { eventName: [...hookEntries] } }
|
|
@@ -23604,12 +23837,14 @@ function copilotHooksToCanonical(copilotHooks, logger) {
|
|
|
23604
23837
|
const entry = parseResult.data;
|
|
23605
23838
|
const { command, shell } = resolveImportCommand$1(entry, logger);
|
|
23606
23839
|
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
23840
|
+
const passthrough = importPassthrough$1(entry);
|
|
23841
|
+
if (command === void 0 && hasOsOverride(passthrough)) logger?.warn(`Copilot hook on '${copilotEventName}' has only VS Code per-OS overrides (windows/linux/osx) and no portable command; it is preserved for copilot, but other targets will generate it without a command until a \`command\` is added.`);
|
|
23607
23842
|
defs.push({
|
|
23608
23843
|
type: "command",
|
|
23609
23844
|
...command !== void 0 && { command },
|
|
23610
23845
|
...shell !== void 0 && { shell },
|
|
23611
23846
|
...timeout !== void 0 && { timeout },
|
|
23612
|
-
...
|
|
23847
|
+
...passthrough
|
|
23613
23848
|
});
|
|
23614
23849
|
}
|
|
23615
23850
|
if (defs.length > 0) canonical[eventName] = defs;
|
|
@@ -23772,6 +24007,8 @@ const CopilotCliHookEntrySchema = zod_mini.z.looseObject({
|
|
|
23772
24007
|
bash: zod_mini.z.optional(zod_mini.z.string()),
|
|
23773
24008
|
powershell: zod_mini.z.optional(zod_mini.z.string()),
|
|
23774
24009
|
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
24010
|
+
exec: zod_mini.z.optional(zod_mini.z.string()),
|
|
24011
|
+
args: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
23775
24012
|
prompt: zod_mini.z.optional(zod_mini.z.string()),
|
|
23776
24013
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
23777
24014
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
@@ -23801,6 +24038,32 @@ function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger
|
|
|
23801
24038
|
return {};
|
|
23802
24039
|
}
|
|
23803
24040
|
/**
|
|
24041
|
+
* Pick the command fields of an exported `command` entry.
|
|
24042
|
+
*
|
|
24043
|
+
* A canonical `args` list (an empty one counts) selects the CLI's shell-free
|
|
24044
|
+
* form: `command` becomes `exec` and the list is written as `args`, so the
|
|
24045
|
+
* executable is spawned directly. The `shell` selector is meaningless there
|
|
24046
|
+
* and is dropped with a warning. Otherwise the canonical `shell` selector
|
|
24047
|
+
* picks the shell-specific field; without it the portable `command` field is
|
|
24048
|
+
* written, which upstream copies to both `bash` and `powershell`. Keying off
|
|
24049
|
+
* `shell` rather than `process.platform` keeps the generated file identical
|
|
24050
|
+
* on every machine.
|
|
24051
|
+
*/
|
|
24052
|
+
function resolveExportCommandPart({ def, eventName, logger }) {
|
|
24053
|
+
if (Array.isArray(def.args)) {
|
|
24054
|
+
if (def.shell !== void 0) logger?.warn(`Copilot CLI runs an \`exec\` hook without a shell; dropping shell "${def.shell}" on '${eventName}'.`);
|
|
24055
|
+
return compact({
|
|
24056
|
+
exec: def.command,
|
|
24057
|
+
args: def.args,
|
|
24058
|
+
env: def.env
|
|
24059
|
+
});
|
|
24060
|
+
}
|
|
24061
|
+
return compact({
|
|
24062
|
+
[def.shell ?? "command"]: def.command,
|
|
24063
|
+
env: def.env
|
|
24064
|
+
});
|
|
24065
|
+
}
|
|
24066
|
+
/**
|
|
23804
24067
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
23805
24068
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
23806
24069
|
*/
|
|
@@ -23840,19 +24103,17 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
23840
24103
|
...timeoutPart,
|
|
23841
24104
|
...rest
|
|
23842
24105
|
});
|
|
23843
|
-
else if (hookType === "command") {
|
|
23844
|
-
|
|
23845
|
-
|
|
23846
|
-
|
|
23847
|
-
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23853
|
-
|
|
23854
|
-
});
|
|
23855
|
-
}
|
|
24106
|
+
else if (hookType === "command") entries.push({
|
|
24107
|
+
type: "command",
|
|
24108
|
+
...matcherPart,
|
|
24109
|
+
...resolveExportCommandPart({
|
|
24110
|
+
def,
|
|
24111
|
+
eventName,
|
|
24112
|
+
logger
|
|
24113
|
+
}),
|
|
24114
|
+
...timeoutPart,
|
|
24115
|
+
...rest
|
|
24116
|
+
});
|
|
23856
24117
|
}
|
|
23857
24118
|
return entries;
|
|
23858
24119
|
}
|
|
@@ -23892,6 +24153,10 @@ function importPassthrough(entry) {
|
|
|
23892
24153
|
/**
|
|
23893
24154
|
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
23894
24155
|
*
|
|
24156
|
+
* The CLI-only `exec` form maps to canonical `command` + `args`, the exec form
|
|
24157
|
+
* shared with AugmentCode and Claude Code, so it no longer imports as a hook
|
|
24158
|
+
* with no command at all.
|
|
24159
|
+
*
|
|
23895
24160
|
* A shell-specific field carries its `shell` through so re-export writes the
|
|
23896
24161
|
* same field back. An entry using only the portable `command` field leaves
|
|
23897
24162
|
* `shell` unset, which re-export renders as the portable field again.
|
|
@@ -23905,6 +24170,13 @@ function importPassthrough(entry) {
|
|
|
23905
24170
|
function resolveImportCommand(entry, logger) {
|
|
23906
24171
|
const hasBash = typeof entry.bash === "string";
|
|
23907
24172
|
const hasPowershell = typeof entry.powershell === "string";
|
|
24173
|
+
if (typeof entry.exec === "string") {
|
|
24174
|
+
if (hasBash || hasPowershell || typeof entry.command === "string") logger?.warn("Copilot CLI hook has both exec and a shell command; using exec and ignoring the shell fields, as the hooks reference says not to combine them.");
|
|
24175
|
+
return {
|
|
24176
|
+
command: entry.exec,
|
|
24177
|
+
args: entry.args ?? []
|
|
24178
|
+
};
|
|
24179
|
+
}
|
|
23908
24180
|
if (hasBash && hasPowershell) {
|
|
23909
24181
|
logger?.warn("Copilot CLI hook has both bash and powershell commands; using bash and ignoring powershell, so the imported config does not depend on the machine the import ran on.");
|
|
23910
24182
|
return {
|
|
@@ -23954,11 +24226,12 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
23954
24226
|
...passthrough
|
|
23955
24227
|
});
|
|
23956
24228
|
else {
|
|
23957
|
-
const { command, shell } = resolveImportCommand(entry, logger);
|
|
24229
|
+
const { command, shell, args } = resolveImportCommand(entry, logger);
|
|
23958
24230
|
defs.push({
|
|
23959
24231
|
type: "command",
|
|
23960
24232
|
...command !== void 0 && { command },
|
|
23961
24233
|
...shell !== void 0 && { shell },
|
|
24234
|
+
...args !== void 0 && { args },
|
|
23962
24235
|
...matcherPart,
|
|
23963
24236
|
...timeoutPart,
|
|
23964
24237
|
...passthrough
|
|
@@ -24508,6 +24781,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
24508
24781
|
...def.loop_limit !== void 0 && { loop_limit: def.loop_limit },
|
|
24509
24782
|
...def.matcher !== void 0 && def.matcher !== null && { matcher: def.matcher },
|
|
24510
24783
|
...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
|
|
24784
|
+
...def.type === "prompt" && def.model !== void 0 && def.model !== null && { model: def.model },
|
|
24511
24785
|
...def.failClosed !== void 0 && def.failClosed !== null && { failClosed: def.failClosed }
|
|
24512
24786
|
}));
|
|
24513
24787
|
if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
|
|
@@ -25210,7 +25484,15 @@ const GOOSE_CONVERTER_CONFIG = {
|
|
|
25210
25484
|
toolToCanonicalEventNames: GOOSE_TO_CANONICAL_EVENT_NAMES,
|
|
25211
25485
|
projectDirVar: "",
|
|
25212
25486
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
25213
|
-
wildcardMatcherMeansAll: true
|
|
25487
|
+
wildcardMatcherMeansAll: true,
|
|
25488
|
+
keywordPassthroughFields: [{
|
|
25489
|
+
canonical: "failClosed",
|
|
25490
|
+
tool: "on_failure",
|
|
25491
|
+
trueValue: "block",
|
|
25492
|
+
falseValue: "allow",
|
|
25493
|
+
commandOnly: true,
|
|
25494
|
+
events: /* @__PURE__ */ new Set(["preToolUse"])
|
|
25495
|
+
}]
|
|
25214
25496
|
};
|
|
25215
25497
|
/**
|
|
25216
25498
|
* Represents a Goose lifecycle hooks file.
|
|
@@ -25519,7 +25801,7 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
25519
25801
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
25520
25802
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
25521
25803
|
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.`);
|
|
25522
|
-
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.
|
|
25804
|
+
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.21.3; preserving it for forward compatibility.`);
|
|
25523
25805
|
setHermesHookEntries({
|
|
25524
25806
|
result,
|
|
25525
25807
|
event: nativeEvent,
|
|
@@ -26399,7 +26681,8 @@ const KiroIdeHookEntrySchema = zod_mini.z.looseObject({
|
|
|
26399
26681
|
matcher: zod_mini.z.optional(zod_mini.z.string()),
|
|
26400
26682
|
action: zod_mini.z.optional(KiroIdeHookActionSchema),
|
|
26401
26683
|
timeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
26402
|
-
enabled: zod_mini.z.optional(zod_mini.z.boolean())
|
|
26684
|
+
enabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
26685
|
+
confirm: zod_mini.z.optional(HookConfirmSchema)
|
|
26403
26686
|
});
|
|
26404
26687
|
const KiroIdeHooksFileSchema = zod_mini.z.looseObject({
|
|
26405
26688
|
version: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -26438,7 +26721,8 @@ function buildKiroIdeEntriesForEvent(trigger, definitions) {
|
|
|
26438
26721
|
...def.matcher !== void 0 && def.matcher !== null && def.matcher !== "" && { matcher: def.matcher },
|
|
26439
26722
|
action,
|
|
26440
26723
|
...def.timeout !== void 0 && def.timeout !== null && def.timeout >= 0 && { timeout: def.timeout },
|
|
26441
|
-
enabled: def.enabled ?? true
|
|
26724
|
+
enabled: def.enabled ?? true,
|
|
26725
|
+
...def.confirm !== void 0 && { confirm: def.confirm }
|
|
26442
26726
|
});
|
|
26443
26727
|
}
|
|
26444
26728
|
return entries;
|
|
@@ -26480,6 +26764,14 @@ function canonicalToKiroIdeHooks(config) {
|
|
|
26480
26764
|
}
|
|
26481
26765
|
return entries;
|
|
26482
26766
|
}
|
|
26767
|
+
/**
|
|
26768
|
+
* Copies the optional per-hook fields (`enabled`, `confirm`) back onto the
|
|
26769
|
+
* canonical definition.
|
|
26770
|
+
*/
|
|
26771
|
+
function copyOptionalHookFields({ entry, def }) {
|
|
26772
|
+
if (entry.enabled === false) def.enabled = false;
|
|
26773
|
+
if (entry.confirm !== void 0 && entry.confirm !== null) def.confirm = omitPrototypePollutionKeysDeep(entry.confirm);
|
|
26774
|
+
}
|
|
26483
26775
|
function kiroIdeHooksToCanonical(entries) {
|
|
26484
26776
|
const canonical = {};
|
|
26485
26777
|
for (const entry of entries) {
|
|
@@ -26503,7 +26795,10 @@ function kiroIdeHooksToCanonical(entries) {
|
|
|
26503
26795
|
if (entry.description !== void 0 && entry.description !== null) def.description = entry.description;
|
|
26504
26796
|
if (entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "") def.matcher = entry.matcher;
|
|
26505
26797
|
if (entry.timeout !== void 0 && entry.timeout !== null) def.timeout = entry.timeout;
|
|
26506
|
-
|
|
26798
|
+
copyOptionalHookFields({
|
|
26799
|
+
entry,
|
|
26800
|
+
def
|
|
26801
|
+
});
|
|
26507
26802
|
const list = lookupOwn({
|
|
26508
26803
|
record: canonical,
|
|
26509
26804
|
key: eventName
|
|
@@ -27510,10 +27805,16 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
27510
27805
|
//#endregion
|
|
27511
27806
|
//#region src/features/hooks/reasonix-hooks.ts
|
|
27512
27807
|
/**
|
|
27513
|
-
* Only
|
|
27514
|
-
* against the tool name); it is ignored on every other event.
|
|
27808
|
+
* Only the tool-scoped events honor the `match` field (an anchored regex
|
|
27809
|
+
* against the tool name); it is ignored on every other event. Mirrors
|
|
27810
|
+
* `UsesToolMatcher` in upstream `internal/hook/inspect.go`.
|
|
27515
27811
|
*/
|
|
27516
|
-
const REASONIX_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
27812
|
+
const REASONIX_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
27813
|
+
"PreToolUse",
|
|
27814
|
+
"PostToolUse",
|
|
27815
|
+
"PostToolUseFailure",
|
|
27816
|
+
"PermissionRequest"
|
|
27817
|
+
]);
|
|
27517
27818
|
const SUPPORTED_REASONIX_EVENTS = new Set(REASONIX_HOOK_EVENTS);
|
|
27518
27819
|
/**
|
|
27519
27820
|
* Convert canonical hooks config to the Reasonix `hooks` object.
|
|
@@ -27595,9 +27896,11 @@ function reasonixHooksToCanonical(hooks) {
|
|
|
27595
27896
|
* Reasonix hooks live in a Claude-Code-style but standalone JSON file —
|
|
27596
27897
|
* `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
|
|
27597
27898
|
* (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
|
|
27598
|
-
* All
|
|
27599
|
-
* PreToolUse/PostToolUse/
|
|
27600
|
-
* SubagentStop/PostLLMCall/
|
|
27899
|
+
* All thirteen upstream events have a clean canonical equivalent and are mapped:
|
|
27900
|
+
* PreToolUse/PostToolUse/PostToolUseFailure/PermissionRequest/UserPromptSubmit/
|
|
27901
|
+
* Stop/StopFailure plus SessionStart/SessionEnd/SubagentStop/PostLLMCall/
|
|
27902
|
+
* Notification/PreCompact (see REASONIX_HOOK_EVENTS).
|
|
27903
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/hook/hook.go
|
|
27601
27904
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
27602
27905
|
*/
|
|
27603
27906
|
var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
@@ -28423,6 +28726,23 @@ const PER_HOOK_ENABLED_TARGETS = /* @__PURE__ */ new Set([
|
|
|
28423
28726
|
"kiro-ide",
|
|
28424
28727
|
"zcode"
|
|
28425
28728
|
]);
|
|
28729
|
+
/** The targets whose hooks format carries the Kiro `confirm` prompt. */
|
|
28730
|
+
const PER_HOOK_CONFIRM_TARGETS = /* @__PURE__ */ new Set(["kiro-cli", "kiro-ide"]);
|
|
28731
|
+
/**
|
|
28732
|
+
* Names the shared events that carry a hook whose canonical definition
|
|
28733
|
+
* satisfies `predicate`, excluding events the target skips altogether (those
|
|
28734
|
+
* are already reported as skipped and produce no output, so warning about them
|
|
28735
|
+
* again would contradict that message).
|
|
28736
|
+
*/
|
|
28737
|
+
function emittedEventsWithHook(params) {
|
|
28738
|
+
const { factory, sharedHooks, effectiveHooks, predicate } = params;
|
|
28739
|
+
const skippedEvents = new Set(unsupportedEventNames({
|
|
28740
|
+
factory,
|
|
28741
|
+
sharedHooks,
|
|
28742
|
+
effectiveHooks
|
|
28743
|
+
}));
|
|
28744
|
+
return Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => predicate(def))).map(([event]) => event);
|
|
28745
|
+
}
|
|
28426
28746
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
28427
28747
|
["amp", {
|
|
28428
28748
|
class: AmpHooks,
|
|
@@ -28993,15 +29313,11 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
28993
29313
|
}
|
|
28994
29314
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
28995
29315
|
}
|
|
28996
|
-
|
|
28997
|
-
|
|
28998
|
-
|
|
28999
|
-
|
|
29000
|
-
|
|
29001
|
-
}));
|
|
29002
|
-
const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
|
|
29003
|
-
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the kiro-cli / kiro-ide / zcode hooks formats support the flag): ${eventsWithDisabledHooks.join(", ")}`);
|
|
29004
|
-
}
|
|
29316
|
+
this.warnAboutDroppedPerHookFields({
|
|
29317
|
+
factory,
|
|
29318
|
+
sharedHooks,
|
|
29319
|
+
effectiveHooks
|
|
29320
|
+
});
|
|
29005
29321
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
29006
29322
|
factory,
|
|
29007
29323
|
effectiveHooks
|
|
@@ -29031,6 +29347,33 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
29031
29347
|
if (auxiliaryFiles && auxiliaryFiles.length > 0) result.push(...auxiliaryFiles);
|
|
29032
29348
|
return result;
|
|
29033
29349
|
}
|
|
29350
|
+
/**
|
|
29351
|
+
* Warns about canonical per-hook fields that this target's hooks format
|
|
29352
|
+
* cannot express, so a hook does not silently change behavior on generation.
|
|
29353
|
+
* Only canonical definitions are considered: a tool-native field inside an
|
|
29354
|
+
* override block is passed through verbatim and honored by that tool.
|
|
29355
|
+
*/
|
|
29356
|
+
warnAboutDroppedPerHookFields(params) {
|
|
29357
|
+
const { factory, sharedHooks, effectiveHooks } = params;
|
|
29358
|
+
if (!PER_HOOK_ENABLED_TARGETS.has(this.toolTarget)) {
|
|
29359
|
+
const eventsWithDisabledHooks = emittedEventsWithHook({
|
|
29360
|
+
factory,
|
|
29361
|
+
sharedHooks,
|
|
29362
|
+
effectiveHooks,
|
|
29363
|
+
predicate: (def) => def.enabled === false
|
|
29364
|
+
});
|
|
29365
|
+
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the kiro-cli / kiro-ide / zcode hooks formats support the flag): ${eventsWithDisabledHooks.join(", ")}`);
|
|
29366
|
+
}
|
|
29367
|
+
if (!PER_HOOK_CONFIRM_TARGETS.has(this.toolTarget)) {
|
|
29368
|
+
const eventsWithConfirmHooks = emittedEventsWithHook({
|
|
29369
|
+
factory,
|
|
29370
|
+
sharedHooks,
|
|
29371
|
+
effectiveHooks,
|
|
29372
|
+
predicate: (def) => def.confirm !== void 0
|
|
29373
|
+
});
|
|
29374
|
+
if (eventsWithConfirmHooks.length > 0) this.logger.warn(`Emitting hook(s) without their "confirm" prompt for ${this.toolTarget} (only the kiro-cli / kiro-ide hooks formats support it): ${eventsWithConfirmHooks.join(", ")}`);
|
|
29375
|
+
}
|
|
29376
|
+
}
|
|
29034
29377
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
29035
29378
|
const hooks = toolFiles.filter((f) => f instanceof ToolHooks);
|
|
29036
29379
|
const logger = withToolTargetPrefix({
|
|
@@ -34832,28 +35175,60 @@ function resolveHermesTimeout(config) {
|
|
|
34832
35175
|
if (typeof config.networkTimeout === "number") return config.networkTimeout;
|
|
34833
35176
|
}
|
|
34834
35177
|
/**
|
|
34835
|
-
*
|
|
34836
|
-
*
|
|
34837
|
-
*
|
|
34838
|
-
* `
|
|
34839
|
-
*
|
|
34840
|
-
*
|
|
34841
|
-
|
|
34842
|
-
|
|
35178
|
+
* String-valued `oauth` keys Hermes reads (`tools/mcp_oauth.py`,
|
|
35179
|
+
* `tools/mcp_oauth_device.py`, v0.21.3): the callback overrides, the
|
|
35180
|
+
* pre-registered client, the CIMD document override, the DCR registration
|
|
35181
|
+
* fields, the token-request `user_agent`, the login `flow` (`device` for
|
|
35182
|
+
* RFC 8628) and the space-separated `scope` string sent on registration and
|
|
35183
|
+
* device authorization.
|
|
35184
|
+
*/
|
|
35185
|
+
const HERMES_OAUTH_STRING_KEYS = [
|
|
35186
|
+
"redirect_uri",
|
|
35187
|
+
"redirect_host",
|
|
35188
|
+
"client_id",
|
|
35189
|
+
"client_secret",
|
|
35190
|
+
"client_name",
|
|
35191
|
+
"client_metadata_url",
|
|
35192
|
+
"token_endpoint_auth_method",
|
|
35193
|
+
"application_type",
|
|
35194
|
+
"user_agent",
|
|
35195
|
+
"flow",
|
|
35196
|
+
"scope"
|
|
35197
|
+
];
|
|
35198
|
+
/**
|
|
35199
|
+
* Copies the `oauth` mapping of a server, keeping only the keys Hermes reads.
|
|
35200
|
+
*
|
|
35201
|
+
* Hermes reads a single `scope` string, never a `scopes` list; earlier Rulesync
|
|
35202
|
+
* versions wrote `scopes` regardless, which Hermes silently ignored. A `scopes`
|
|
35203
|
+
* list is therefore folded into `scope` (space-separated, the OAuth wire form)
|
|
35204
|
+
* when no `scope` is given, in both directions, so an authored list finally
|
|
35205
|
+
* reaches the authorization server and an old `config.yaml` imports cleanly.
|
|
34843
35206
|
*/
|
|
34844
35207
|
function copyHermesOauth(source) {
|
|
34845
35208
|
if (!isRecord$1(source)) return;
|
|
34846
35209
|
const oauth = {};
|
|
34847
|
-
for (const key of [
|
|
34848
|
-
|
|
34849
|
-
|
|
34850
|
-
|
|
34851
|
-
"client_secret"
|
|
34852
|
-
]) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
34853
|
-
if (typeof source.redirect_port === "number") oauth.redirect_port = source.redirect_port;
|
|
34854
|
-
if (isStringArray$2(source.scopes)) oauth.scopes = source.scopes;
|
|
35210
|
+
for (const key of HERMES_OAUTH_STRING_KEYS) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
35211
|
+
if (typeof oauth.scope !== "string" && isStringArray$2(source.scopes)) oauth.scope = source.scopes.join(" ");
|
|
35212
|
+
for (const key of ["redirect_port", "timeout"]) if (typeof source[key] === "number") oauth[key] = source[key];
|
|
35213
|
+
if (typeof source.cimd === "boolean") oauth.cimd = source.cimd;
|
|
34855
35214
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
34856
35215
|
}
|
|
35216
|
+
const HERMES_OPAQUE_MAPPING_KEYS = [
|
|
35217
|
+
"sampling",
|
|
35218
|
+
"elicitation",
|
|
35219
|
+
"identity_header",
|
|
35220
|
+
"lifecycle"
|
|
35221
|
+
];
|
|
35222
|
+
/**
|
|
35223
|
+
* Copies the advanced Hermes-recognized per-server fields that have no canonical
|
|
35224
|
+
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
35225
|
+
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
35226
|
+
* `connect_timeout` (seconds), `supports_parallel_tool_calls`, `protocol`,
|
|
35227
|
+
* `lazy`, `keepalive_interval`, `elicitation`, `trust`, and `identity_header` —
|
|
35228
|
+
* verbatim from `source` to `target`. Field names are identical on both sides
|
|
35229
|
+
* (the canonical `McpServerSchema` is a `looseObject`), so this serves export
|
|
35230
|
+
* and import alike. See the Hermes mcp-config-reference.
|
|
35231
|
+
*/
|
|
34857
35232
|
function copyHermesAdvancedFields(source, target) {
|
|
34858
35233
|
let copied = false;
|
|
34859
35234
|
if (typeof source.auth === "string") {
|
|
@@ -34885,6 +35260,14 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
34885
35260
|
target[key] = source[key];
|
|
34886
35261
|
copied = true;
|
|
34887
35262
|
}
|
|
35263
|
+
if (typeof source.protocol === "string") {
|
|
35264
|
+
target.protocol = source.protocol;
|
|
35265
|
+
copied = true;
|
|
35266
|
+
}
|
|
35267
|
+
if (typeof source.lazy === "boolean") {
|
|
35268
|
+
target.lazy = source.lazy;
|
|
35269
|
+
copied = true;
|
|
35270
|
+
}
|
|
34888
35271
|
if (typeof source.ssl_verify === "boolean" || typeof source.ssl_verify === "string") {
|
|
34889
35272
|
target.ssl_verify = source.ssl_verify;
|
|
34890
35273
|
copied = true;
|
|
@@ -34893,25 +35276,20 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
34893
35276
|
target.skip_preflight = source.skip_preflight;
|
|
34894
35277
|
copied = true;
|
|
34895
35278
|
}
|
|
34896
|
-
if (isPlainObject$1(source.sampling)) {
|
|
34897
|
-
target.sampling = omitPrototypePollutionKeys(structuredClone(source.sampling));
|
|
34898
|
-
copied = true;
|
|
34899
|
-
}
|
|
34900
35279
|
if (typeof source.keepalive_interval === "number") {
|
|
34901
35280
|
target.keepalive_interval = source.keepalive_interval;
|
|
34902
35281
|
copied = true;
|
|
34903
35282
|
}
|
|
34904
|
-
if (isPlainObject$1(source.elicitation)) {
|
|
34905
|
-
target.elicitation = omitPrototypePollutionKeys(structuredClone(source.elicitation));
|
|
34906
|
-
copied = true;
|
|
34907
|
-
}
|
|
34908
35283
|
if (typeof source.trust === "string") {
|
|
34909
35284
|
target.trust = source.trust;
|
|
34910
35285
|
copied = true;
|
|
34911
35286
|
}
|
|
34912
|
-
|
|
34913
|
-
|
|
34914
|
-
|
|
35287
|
+
for (const key of HERMES_OPAQUE_MAPPING_KEYS) {
|
|
35288
|
+
const mapping = source[key];
|
|
35289
|
+
if (isPlainObject$1(mapping)) {
|
|
35290
|
+
target[key] = omitPrototypePollutionKeys(structuredClone(mapping));
|
|
35291
|
+
copied = true;
|
|
35292
|
+
}
|
|
34915
35293
|
}
|
|
34916
35294
|
return copied;
|
|
34917
35295
|
}
|
|
@@ -35695,6 +36073,7 @@ function toKimiCodeServer({ name, server, logger }) {
|
|
|
35695
36073
|
"headers",
|
|
35696
36074
|
"bearerTokenEnvVar",
|
|
35697
36075
|
"enabled",
|
|
36076
|
+
"deferred",
|
|
35698
36077
|
"startupTimeoutMs",
|
|
35699
36078
|
"toolTimeoutMs",
|
|
35700
36079
|
"enabledTools",
|
|
@@ -36352,7 +36731,12 @@ const OpencodeMcpRemoteServerSchema = zod_mini.z.looseObject({
|
|
|
36352
36731
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
36353
36732
|
enabled: zod_mini.z._default(zod_mini.z.boolean(), true)
|
|
36354
36733
|
});
|
|
36355
|
-
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = [
|
|
36734
|
+
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = [
|
|
36735
|
+
"timeout",
|
|
36736
|
+
"oauth",
|
|
36737
|
+
"protocol",
|
|
36738
|
+
"codemode"
|
|
36739
|
+
];
|
|
36356
36740
|
const OPENCODE_MCP_TRANSPORT_KEYS = [
|
|
36357
36741
|
...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
|
|
36358
36742
|
...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
|
|
@@ -36494,7 +36878,7 @@ function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
|
36494
36878
|
* - env -> environment
|
|
36495
36879
|
* - disabled -> enabled (inverted)
|
|
36496
36880
|
* - enabledTools/disabledTools -> top-level tools map (with server name prefix)
|
|
36497
|
-
* - OpenCode-supported extras (timeout, oauth) -> passed through verbatim
|
|
36881
|
+
* - OpenCode-supported extras (timeout, oauth, protocol, codemode) -> passed through verbatim
|
|
36498
36882
|
*/
|
|
36499
36883
|
function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, logger) {
|
|
36500
36884
|
const serverRecord = serverConfig;
|
|
@@ -39487,7 +39871,7 @@ var ToolPermissions = class extends ToolFile {
|
|
|
39487
39871
|
* builtin of that name). It lives in the shared Amp settings file:
|
|
39488
39872
|
* `.amp/settings.json` (project) and `~/.config/amp/settings.json` (global).
|
|
39489
39873
|
*
|
|
39490
|
-
* Reference: https://ampcode.com/
|
|
39874
|
+
* Reference: https://ampcode.com/docs/cli/settings ("amp.tools.disable").
|
|
39491
39875
|
*/
|
|
39492
39876
|
const AMP_TOOLS_DISABLE_KEY = "amp.tools.disable";
|
|
39493
39877
|
/**
|
|
@@ -39498,19 +39882,26 @@ const AMP_TOOLS_DISABLE_KEY = "amp.tools.disable";
|
|
|
39498
39882
|
* glob. Rules are evaluated **first-match-wins**. It lives in the same shared
|
|
39499
39883
|
* Amp settings file as `amp.tools.disable`.
|
|
39500
39884
|
*
|
|
39501
|
-
* `amp.permissions` is Amp's
|
|
39502
|
-
*
|
|
39503
|
-
* argument-specific `reject` rules (the simpler `amp.tools.disable` array
|
|
39504
|
-
* only disable whole tools).
|
|
39885
|
+
* `amp.permissions` is Amp's legacy/backwards-compat surface — it remains
|
|
39886
|
+
* functional in the shipped CLI and is the only place to express `allow`/`ask`
|
|
39887
|
+
* and argument-specific `reject` rules (the simpler `amp.tools.disable` array
|
|
39888
|
+
* can only disable whole tools). The web docs no longer describe it (the
|
|
39889
|
+
* permissions section only points at custom plugins), but the shipped CLI
|
|
39890
|
+
* still lists it in its built-in settings reference (`amp --help`) and
|
|
39891
|
+
* evaluates it through `amp permissions list|test`.
|
|
39505
39892
|
*
|
|
39506
|
-
* Reference: https://ampcode.com/
|
|
39893
|
+
* Reference: https://ampcode.com/docs/tools#permissions and
|
|
39894
|
+
* https://ampcode.com/docs/customize/plugins#example-plugin-permissions.
|
|
39507
39895
|
*/
|
|
39508
39896
|
const AMP_PERMISSIONS_KEY = "amp.permissions";
|
|
39509
39897
|
/**
|
|
39510
39898
|
* The `amp.guardedFiles.allowlist` array (file globs allowed without
|
|
39511
39899
|
* confirmation), `amp.dangerouslyAllowAll` boolean (disable all confirmation),
|
|
39512
39900
|
* and `amp.mcpPermissions` array — sibling settings authored through the `amp`
|
|
39513
|
-
* permissions override.
|
|
39901
|
+
* permissions override. Only `amp.mcpPermissions` is still on the web docs
|
|
39902
|
+
* (https://ampcode.com/docs/cli/settings); the other two, like
|
|
39903
|
+
* `amp.permissions`, survive only in the CLI's built-in settings reference
|
|
39904
|
+
* (see `AMP_PERMISSIONS_KEY`).
|
|
39514
39905
|
*/
|
|
39515
39906
|
const AMP_GUARDED_FILES_ALLOWLIST_KEY = "amp.guardedFiles.allowlist";
|
|
39516
39907
|
const AMP_DANGEROUSLY_ALLOW_ALL_KEY = "amp.dangerouslyAllowAll";
|
|
@@ -42839,6 +43230,8 @@ const CODEX_DANGER_FULL_ACCESS_BASELINE = ":danger-full-access";
|
|
|
42839
43230
|
const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_EXTENDABLE_BASELINE_PROFILES);
|
|
42840
43231
|
const CODEX_DEFAULT_APPROVAL_POLICY = "on-request";
|
|
42841
43232
|
const CODEX_DEFAULT_APPROVALS_REVIEWER = "auto_review";
|
|
43233
|
+
const CODEX_RETIRED_APPROVAL_POLICY = "untrusted";
|
|
43234
|
+
const CODEX_DEPRECATED_APPROVAL_POLICY = "on-failure";
|
|
42842
43235
|
const CODEX_GLOB_SCAN_MAX_DEPTH = 8;
|
|
42843
43236
|
const CODEX_MINIMAL_KEY = ":minimal";
|
|
42844
43237
|
const CODEX_GIT_WRITE_RULES = { ".git/**": "write" };
|
|
@@ -42962,6 +43355,10 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
42962
43355
|
domainsHadUnknown
|
|
42963
43356
|
});
|
|
42964
43357
|
const override = extractCodexcliOverride(table);
|
|
43358
|
+
if (override.approval_policy === CODEX_RETIRED_APPROVAL_POLICY) {
|
|
43359
|
+
warnWithFallback(void 0, `${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())} sets approval_policy = "${CODEX_RETIRED_APPROVAL_POLICY}", which was retired in Codex 0.149.0 and makes Codex refuse to start. It was not imported; remove it from the file, or mark the project untrusted in your user config instead ([projects."<path>"] trust_level = "${CODEX_RETIRED_APPROVAL_POLICY}").`);
|
|
43360
|
+
delete override.approval_policy;
|
|
43361
|
+
}
|
|
42965
43362
|
if (typeof profile?.extends === "string" && CODEX_EXTENDABLE_BASELINES.has(profile.extends)) override.base_permission_profile = profile.extends;
|
|
42966
43363
|
if (defaultProfile === CODEX_DANGER_FULL_ACCESS_BASELINE) override.base_permission_profile = CODEX_DANGER_FULL_ACCESS_BASELINE;
|
|
42967
43364
|
const result = Object.keys(override).length > 0 ? {
|
|
@@ -43250,6 +43647,10 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
|
|
|
43250
43647
|
continue;
|
|
43251
43648
|
}
|
|
43252
43649
|
if (value === void 0) continue;
|
|
43650
|
+
if (key === "approval_policy" && !isWritableApprovalPolicy({
|
|
43651
|
+
value,
|
|
43652
|
+
logger
|
|
43653
|
+
})) continue;
|
|
43253
43654
|
if (key === "sandbox_mode" || key === "sandbox_workspace_write") logger?.warn(`Codex CLI permission override key "${key}" is deprecated. Codex prioritizes the legacy sandbox settings over permission profiles when both are present, so it disables the generated "${RULESYNC_PROFILE_NAME}" permissions profile. Use "base_permission_profile" and the shared "permission" block instead.`);
|
|
43254
43655
|
const existingValue = existing[key];
|
|
43255
43656
|
patch[key] = isPlainObject(existingValue) && isPlainObject(value) ? {
|
|
@@ -43257,12 +43658,37 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
|
|
|
43257
43658
|
...value
|
|
43258
43659
|
} : value;
|
|
43259
43660
|
}
|
|
43661
|
+
fillCodexcliDefaults({
|
|
43662
|
+
existing,
|
|
43663
|
+
patch,
|
|
43664
|
+
logger
|
|
43665
|
+
});
|
|
43666
|
+
return patch;
|
|
43667
|
+
}
|
|
43668
|
+
function fillCodexcliDefaults({ existing, patch, logger }) {
|
|
43260
43669
|
const defaults = {
|
|
43261
43670
|
approval_policy: CODEX_DEFAULT_APPROVAL_POLICY,
|
|
43262
43671
|
approvals_reviewer: CODEX_DEFAULT_APPROVALS_REVIEWER
|
|
43263
43672
|
};
|
|
43264
|
-
for (const [key, value] of Object.entries(defaults))
|
|
43265
|
-
|
|
43673
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
43674
|
+
if (patch[key] !== void 0) continue;
|
|
43675
|
+
if (existing[key] === void 0) {
|
|
43676
|
+
patch[key] = value;
|
|
43677
|
+
continue;
|
|
43678
|
+
}
|
|
43679
|
+
if (key === "approval_policy" && existing[key] === CODEX_RETIRED_APPROVAL_POLICY) {
|
|
43680
|
+
logger?.warn(`The existing Codex CLI config.toml sets approval_policy = "${CODEX_RETIRED_APPROVAL_POLICY}", which was retired in Codex 0.149.0 and makes Codex refuse to start, so it was replaced with "${value}". To keep the strict behavior, mark the project untrusted in your user config instead ([projects."<path>"] trust_level = "${CODEX_RETIRED_APPROVAL_POLICY}").`);
|
|
43681
|
+
patch[key] = value;
|
|
43682
|
+
}
|
|
43683
|
+
}
|
|
43684
|
+
}
|
|
43685
|
+
function isWritableApprovalPolicy({ value, logger }) {
|
|
43686
|
+
if (value === CODEX_RETIRED_APPROVAL_POLICY) {
|
|
43687
|
+
logger?.warn(`Codex CLI permission override "approval_policy": "${CODEX_RETIRED_APPROVAL_POLICY}" was retired in Codex 0.149.0 and makes Codex refuse to start, so it was not written. Remove it from the override; to keep the strict behavior, mark the project untrusted in your user config instead ([projects."<path>"] trust_level = "${CODEX_RETIRED_APPROVAL_POLICY}").`);
|
|
43688
|
+
return false;
|
|
43689
|
+
}
|
|
43690
|
+
if (value === CODEX_DEPRECATED_APPROVAL_POLICY) logger?.warn(`Codex CLI permission override "approval_policy": "${CODEX_DEPRECATED_APPROVAL_POLICY}" is deprecated; Codex reads it as "${CODEX_DEFAULT_APPROVAL_POLICY}". Use "${CODEX_DEFAULT_APPROVAL_POLICY}" instead.`);
|
|
43691
|
+
return true;
|
|
43266
43692
|
}
|
|
43267
43693
|
function extractCodexcliOverride(table) {
|
|
43268
43694
|
const override = {};
|
|
@@ -51714,6 +52140,18 @@ const TAKT_RUNTIME_TARGETS_KEY = "targets";
|
|
|
51714
52140
|
const TAKT_RUNTIME_AUTO_ROUTING_KEY = "auto_routing";
|
|
51715
52141
|
const TAKT_RUNTIME_PROFILE_KEY = "profile";
|
|
51716
52142
|
const TAKT_RUNTIME_OPTIONS_KEY = "options";
|
|
52143
|
+
const TAKT_RUNTIME_ASSIGNMENTS_KEY = "assignments";
|
|
52144
|
+
const TAKT_RUNTIME_LADDER_KEY = "ladder";
|
|
52145
|
+
const TAKT_RUNTIME_EXTENDS_KEY = "extends";
|
|
52146
|
+
const TAKT_RUNTIME_ROUTER_PROFILE_KEY = "router_profile";
|
|
52147
|
+
const TAKT_RUNTIME_POOLS_KEY = "pools";
|
|
52148
|
+
const TAKT_RUNTIME_CANDIDATES_KEY = "candidates";
|
|
52149
|
+
const TAKT_RUNTIME_FALLBACK_PROFILE_KEY = "fallback_profile";
|
|
52150
|
+
/** The one `targets` map that routes companion reviewers rather than agents. */
|
|
52151
|
+
const TAKT_RUNTIME_COMPANIONS_TARGET_KEY = "companions";
|
|
52152
|
+
const TAKT_RUNTIME_COMPANION_KEY = "companion";
|
|
52153
|
+
const TAKT_RUNTIME_ENABLED_KEY = "enabled";
|
|
52154
|
+
const TAKT_DEFAULT_COMPANION_ENABLED = false;
|
|
51717
52155
|
const TAKT_SECURITY_POLICIES = {
|
|
51718
52156
|
workflow_arpeggio: [
|
|
51719
52157
|
"custom_data_source_modules",
|
|
@@ -51967,21 +52405,156 @@ function runtimeProfiles(runtime) {
|
|
|
51967
52405
|
const profiles = provider[TAKT_RUNTIME_PROFILES_KEY];
|
|
51968
52406
|
return isPlainObject$1(profiles) ? profiles : {};
|
|
51969
52407
|
}
|
|
52408
|
+
/** A `targets` map (`personas`, `tags`, …) with at least one non-empty entry. */
|
|
52409
|
+
function hasTargetContent(targets) {
|
|
52410
|
+
return isPlainObject$1(targets) && Object.values(targets).some(hasEntries);
|
|
52411
|
+
}
|
|
52412
|
+
/** The profile names an assignment points at: `profile` plus every `ladder` rung. */
|
|
52413
|
+
function assignmentProfileNames(assignment) {
|
|
52414
|
+
if (!isPlainObject$1(assignment)) return [];
|
|
52415
|
+
const names = [];
|
|
52416
|
+
const profile = assignment[TAKT_RUNTIME_PROFILE_KEY];
|
|
52417
|
+
if (typeof profile === "string") names.push(profile);
|
|
52418
|
+
const ladder = assignment[TAKT_RUNTIME_LADDER_KEY];
|
|
52419
|
+
if (Array.isArray(ladder)) names.push(...ladder.filter((rung) => typeof rung === "string"));
|
|
52420
|
+
return names;
|
|
52421
|
+
}
|
|
52422
|
+
/** The assignments under a `targets` map, split by whether they route companions. */
|
|
52423
|
+
function targetAssignments(targets, { companions }) {
|
|
52424
|
+
if (!isPlainObject$1(targets)) return [];
|
|
52425
|
+
return Object.entries(targets).filter(([name]) => name === TAKT_RUNTIME_COMPANIONS_TARGET_KEY === companions).flatMap(([, targetMap]) => isPlainObject$1(targetMap) ? Object.values(targetMap) : []);
|
|
52426
|
+
}
|
|
52427
|
+
/**
|
|
52428
|
+
* Record the profiles a `defaults` + `targets` pair (the top-level provider
|
|
52429
|
+
* section or one named assignment set) reaches, keeping companion roots apart
|
|
52430
|
+
* from the roots agents use.
|
|
52431
|
+
*/
|
|
52432
|
+
function addAssignmentSetRoots({ set, companionRoots, agentRoots }) {
|
|
52433
|
+
for (const name of assignmentProfileNames(set[TAKT_RUNTIME_DEFAULTS_KEY])) agentRoots.add(name);
|
|
52434
|
+
const targets = set[TAKT_RUNTIME_TARGETS_KEY];
|
|
52435
|
+
for (const assignment of targetAssignments(targets, { companions: false })) for (const name of assignmentProfileNames(assignment)) agentRoots.add(name);
|
|
52436
|
+
for (const assignment of targetAssignments(targets, { companions: true })) for (const name of assignmentProfileNames(assignment)) companionRoots.add(name);
|
|
52437
|
+
}
|
|
52438
|
+
/** The profiles `auto_routing` reaches: the router plus every pool candidate/fallback. */
|
|
52439
|
+
function addAutoRoutingRoots(autoRouting, roots) {
|
|
52440
|
+
if (!isPlainObject$1(autoRouting)) return;
|
|
52441
|
+
const routerProfile = autoRouting[TAKT_RUNTIME_ROUTER_PROFILE_KEY];
|
|
52442
|
+
if (typeof routerProfile === "string") roots.add(routerProfile);
|
|
52443
|
+
const pools = autoRouting[TAKT_RUNTIME_POOLS_KEY];
|
|
52444
|
+
if (!isPlainObject$1(pools)) return;
|
|
52445
|
+
for (const pool of Object.values(pools)) {
|
|
52446
|
+
if (!isPlainObject$1(pool)) continue;
|
|
52447
|
+
const candidates = pool[TAKT_RUNTIME_CANDIDATES_KEY];
|
|
52448
|
+
for (const candidate of Array.isArray(candidates) ? candidates : []) for (const name of assignmentProfileNames(candidate)) roots.add(name);
|
|
52449
|
+
const fallback = pool[TAKT_RUNTIME_FALLBACK_PROFILE_KEY];
|
|
52450
|
+
if (typeof fallback === "string") roots.add(fallback);
|
|
52451
|
+
}
|
|
52452
|
+
}
|
|
52453
|
+
/** Every profile reachable from `roots` by following `extends`. */
|
|
52454
|
+
function profileClosure(profiles, roots) {
|
|
52455
|
+
const closure = /* @__PURE__ */ new Set();
|
|
52456
|
+
const visit = (name) => {
|
|
52457
|
+
if (closure.has(name)) return;
|
|
52458
|
+
closure.add(name);
|
|
52459
|
+
const profile = profiles[name];
|
|
52460
|
+
const parent = isPlainObject$1(profile) ? profile[TAKT_RUNTIME_EXTENDS_KEY] : void 0;
|
|
52461
|
+
if (typeof parent === "string") visit(parent);
|
|
52462
|
+
};
|
|
52463
|
+
for (const root of roots) visit(root);
|
|
52464
|
+
return closure;
|
|
52465
|
+
}
|
|
52466
|
+
/** The `targets` map minus its `companions` entry (`undefined` when not a map). */
|
|
52467
|
+
function withoutCompanionTargets(targets) {
|
|
52468
|
+
if (!isPlainObject$1(targets)) return;
|
|
52469
|
+
const { [TAKT_RUNTIME_COMPANIONS_TARGET_KEY]: _companions, ...remaining } = targets;
|
|
52470
|
+
return remaining;
|
|
52471
|
+
}
|
|
52472
|
+
/**
|
|
52473
|
+
* The named assignment sets with their companion targets removed; a set left
|
|
52474
|
+
* with neither `defaults` nor a non-empty `targets` map is dropped, and
|
|
52475
|
+
* `undefined` means none survived.
|
|
52476
|
+
*/
|
|
52477
|
+
function withoutCompanionAssignments(assignments) {
|
|
52478
|
+
if (!isPlainObject$1(assignments)) return;
|
|
52479
|
+
const remaining = {};
|
|
52480
|
+
for (const [name, set] of Object.entries(assignments)) {
|
|
52481
|
+
if (!isPlainObject$1(set)) continue;
|
|
52482
|
+
const defaults = set[TAKT_RUNTIME_DEFAULTS_KEY];
|
|
52483
|
+
const targets = withoutCompanionTargets(set[TAKT_RUNTIME_TARGETS_KEY]);
|
|
52484
|
+
const keepTargets = hasTargetContent(targets);
|
|
52485
|
+
if (defaults === void 0 && !keepTargets) continue;
|
|
52486
|
+
remaining[name] = {
|
|
52487
|
+
...defaults === void 0 ? {} : { [TAKT_RUNTIME_DEFAULTS_KEY]: defaults },
|
|
52488
|
+
...keepTargets ? { [TAKT_RUNTIME_TARGETS_KEY]: targets } : {}
|
|
52489
|
+
};
|
|
52490
|
+
}
|
|
52491
|
+
return Object.keys(remaining).length > 0 ? remaining : void 0;
|
|
52492
|
+
}
|
|
52493
|
+
/** Whether the section (or any of its named assignment sets) routes companions at all. */
|
|
52494
|
+
function hasCompanionTargets(section) {
|
|
52495
|
+
const routesCompanions = (set) => {
|
|
52496
|
+
const targets = isPlainObject$1(set) ? set[TAKT_RUNTIME_TARGETS_KEY] : void 0;
|
|
52497
|
+
return isPlainObject$1(targets) && targets[TAKT_RUNTIME_COMPANIONS_TARGET_KEY] !== void 0;
|
|
52498
|
+
};
|
|
52499
|
+
const assignments = section[TAKT_RUNTIME_ASSIGNMENTS_KEY];
|
|
52500
|
+
return routesCompanions(section) || isPlainObject$1(assignments) && Object.values(assignments).some(routesCompanions);
|
|
52501
|
+
}
|
|
52502
|
+
/**
|
|
52503
|
+
* The `provider:` section as Takt itself evaluates it, mirroring upstream's
|
|
52504
|
+
* `getEffectiveProviderSection`: unless `companion.enabled` is `true`
|
|
52505
|
+
* (companions are opt-in), the `targets.companions` maps — top-level and inside
|
|
52506
|
+
* every named assignment set — are dropped, along with each profile that only a
|
|
52507
|
+
* companion target reaches through `extends`. A profile an agent assignment
|
|
52508
|
+
* (`defaults`, the other `targets` maps, `auto_routing`) also reaches is kept.
|
|
52509
|
+
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/schema.ts
|
|
52510
|
+
*/
|
|
52511
|
+
function effectiveRuntimeProviderSection(runtime) {
|
|
52512
|
+
const section = runtime[TAKT_RUNTIME_PROVIDER_KEY];
|
|
52513
|
+
if (!isPlainObject$1(section)) return;
|
|
52514
|
+
const companion = runtime[TAKT_RUNTIME_COMPANION_KEY];
|
|
52515
|
+
if (((isPlainObject$1(companion) ? companion[TAKT_RUNTIME_ENABLED_KEY] : void 0) ?? TAKT_DEFAULT_COMPANION_ENABLED) === true || !hasCompanionTargets(section)) return section;
|
|
52516
|
+
const profiles = runtimeProfiles(runtime);
|
|
52517
|
+
const companionRoots = /* @__PURE__ */ new Set();
|
|
52518
|
+
const agentRoots = /* @__PURE__ */ new Set();
|
|
52519
|
+
addAssignmentSetRoots({
|
|
52520
|
+
set: section,
|
|
52521
|
+
companionRoots,
|
|
52522
|
+
agentRoots
|
|
52523
|
+
});
|
|
52524
|
+
const assignments = section[TAKT_RUNTIME_ASSIGNMENTS_KEY];
|
|
52525
|
+
for (const set of isPlainObject$1(assignments) ? Object.values(assignments) : []) if (isPlainObject$1(set)) addAssignmentSetRoots({
|
|
52526
|
+
set,
|
|
52527
|
+
companionRoots,
|
|
52528
|
+
agentRoots
|
|
52529
|
+
});
|
|
52530
|
+
addAutoRoutingRoots(section[TAKT_RUNTIME_AUTO_ROUTING_KEY], agentRoots);
|
|
52531
|
+
const companionClosure = profileClosure(profiles, companionRoots);
|
|
52532
|
+
const agentClosure = profileClosure(profiles, agentRoots);
|
|
52533
|
+
const effective = {
|
|
52534
|
+
...section,
|
|
52535
|
+
[TAKT_RUNTIME_PROFILES_KEY]: Object.fromEntries(Object.entries(profiles).filter(([name]) => !companionClosure.has(name) || agentClosure.has(name)))
|
|
52536
|
+
};
|
|
52537
|
+
for (const [key, value] of [[TAKT_RUNTIME_TARGETS_KEY, withoutCompanionTargets(section[TAKT_RUNTIME_TARGETS_KEY])], [TAKT_RUNTIME_ASSIGNMENTS_KEY, withoutCompanionAssignments(assignments)]]) if (value === void 0) delete effective[key];
|
|
52538
|
+
else effective[key] = value;
|
|
52539
|
+
return effective;
|
|
52540
|
+
}
|
|
51970
52541
|
/**
|
|
51971
52542
|
* Whether a parsed `runtime.yaml` puts Takt into runtime mode, mirroring
|
|
51972
|
-
* upstream's mode detection: the `provider:` section
|
|
51973
|
-
*
|
|
51974
|
-
*
|
|
51975
|
-
*
|
|
51976
|
-
*
|
|
52543
|
+
* upstream's mode detection: the effective `provider:` section (see
|
|
52544
|
+
* `effectiveRuntimeProviderSection` — companion-only routing does not count
|
|
52545
|
+
* while companions are disabled) must carry an actual assignment — a non-empty
|
|
52546
|
+
* `defaults`, `profiles` or `auto_routing`, a `targets` map with at least one
|
|
52547
|
+
* non-empty nested map, or a named assignment set with a non-empty `defaults`
|
|
52548
|
+
* or such a `targets` map. The file existing is not enough, and empty nested
|
|
52549
|
+
* maps (`defaults: {}`, `targets: { personas: {} }`) must not flip the mode.
|
|
51977
52550
|
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/mode.ts
|
|
51978
52551
|
*/
|
|
51979
52552
|
function isRuntimeModeActive(runtime) {
|
|
51980
|
-
const
|
|
51981
|
-
if (
|
|
51982
|
-
|
|
51983
|
-
const
|
|
51984
|
-
return
|
|
52553
|
+
const section = effectiveRuntimeProviderSection(runtime);
|
|
52554
|
+
if (section === void 0) return false;
|
|
52555
|
+
const assignments = section[TAKT_RUNTIME_ASSIGNMENTS_KEY];
|
|
52556
|
+
const hasAssignments = isPlainObject$1(assignments) && Object.values(assignments).some((set) => isPlainObject$1(set) && (hasEntries(set[TAKT_RUNTIME_DEFAULTS_KEY]) || hasTargetContent(set[TAKT_RUNTIME_TARGETS_KEY])));
|
|
52557
|
+
return hasEntries(section[TAKT_RUNTIME_DEFAULTS_KEY]) || hasEntries(section[TAKT_RUNTIME_PROFILES_KEY]) || hasEntries(section[TAKT_RUNTIME_AUTO_ROUTING_KEY]) || hasTargetContent(section[TAKT_RUNTIME_TARGETS_KEY]) || hasAssignments;
|
|
51985
52558
|
}
|
|
51986
52559
|
/** The provider named by the runtime profile Takt would use by default. */
|
|
51987
52560
|
function resolveRuntimeProvider(runtime) {
|
|
@@ -52004,10 +52577,13 @@ function resolveRuntimeProvider(runtime) {
|
|
|
52004
52577
|
* shape the `takt` override uses. Each profile's options belong to that
|
|
52005
52578
|
* profile's own provider; when several profiles name the same provider they are
|
|
52006
52579
|
* merged in document order, so a later profile wins on a colliding option key.
|
|
52580
|
+
* Profiles Takt itself ignores (companion-only ones while companions are
|
|
52581
|
+
* disabled) are skipped, as they never reach an agent.
|
|
52007
52582
|
*/
|
|
52008
52583
|
function collectRuntimeProviderOptions(runtime) {
|
|
52009
52584
|
const collected = {};
|
|
52010
|
-
|
|
52585
|
+
const profiles = effectiveRuntimeProviderSection(runtime)?.[TAKT_RUNTIME_PROFILES_KEY];
|
|
52586
|
+
for (const profile of Object.values(isPlainObject$1(profiles) ? profiles : {})) {
|
|
52011
52587
|
if (!isPlainObject$1(profile)) continue;
|
|
52012
52588
|
const providerName = profile[TAKT_RUNTIME_PROVIDER_KEY];
|
|
52013
52589
|
const options = profile[TAKT_RUNTIME_OPTIONS_KEY];
|
|
@@ -52046,13 +52622,20 @@ async function readTaktRuntimeFile({ filePath, logger }) {
|
|
|
52046
52622
|
};
|
|
52047
52623
|
}
|
|
52048
52624
|
}
|
|
52625
|
+
/** A map-valued key of a `provider:` section, or `undefined` when it is not a map. */
|
|
52626
|
+
function runtimeSectionMap(section, key) {
|
|
52627
|
+
return isPlainObject$1(section[key]) ? section[key] : void 0;
|
|
52628
|
+
}
|
|
52049
52629
|
/**
|
|
52050
52630
|
* Collapse the project and global `runtime.yaml` into the single document Takt
|
|
52051
|
-
* itself resolves against, matching upstream's loader: `profiles`
|
|
52052
|
-
* with the project definition of a same-named
|
|
52053
|
-
* while `defaults`, `targets` and `auto_routing` are
|
|
52054
|
-
* whole whenever it states them at all (`??`, so a
|
|
52055
|
-
* the global one rather than merging with it).
|
|
52631
|
+
* itself resolves against, matching upstream's loader: `profiles` and
|
|
52632
|
+
* `assignments` are unions with the project definition of a same-named entry
|
|
52633
|
+
* replacing the global one, while `defaults`, `targets` and `auto_routing` are
|
|
52634
|
+
* taken from the project file whole whenever it states them at all (`??`, so a
|
|
52635
|
+
* project `targets: {}` masks the global one rather than merging with it). The
|
|
52636
|
+
* top-level `companion.enabled` — which decides whether companion routing
|
|
52637
|
+
* counts for mode detection — is carried as upstream carries it: unset when
|
|
52638
|
+
* neither file states it, otherwise on only when neither file turns it off.
|
|
52056
52639
|
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/loader.ts
|
|
52057
52640
|
*
|
|
52058
52641
|
* Merging before mode detection matters in both directions: a project file
|
|
@@ -52068,14 +52651,15 @@ function mergeTaktRuntimeConfigs({ project, global: globalConfig }) {
|
|
|
52068
52651
|
const projectSection = sectionOf(project);
|
|
52069
52652
|
const globalSection = sectionOf(globalConfig);
|
|
52070
52653
|
const replaced = (key) => projectSection[key] ?? globalSection[key];
|
|
52071
|
-
const profilesOf = (section) => isPlainObject$1(section[TAKT_RUNTIME_PROFILES_KEY]) ? section[TAKT_RUNTIME_PROFILES_KEY] : void 0;
|
|
52072
|
-
const globalProfiles = profilesOf(globalSection);
|
|
52073
|
-
const projectProfiles = profilesOf(projectSection);
|
|
52074
52654
|
const provider = {};
|
|
52075
|
-
|
|
52076
|
-
|
|
52077
|
-
|
|
52078
|
-
|
|
52655
|
+
for (const key of [TAKT_RUNTIME_PROFILES_KEY, TAKT_RUNTIME_ASSIGNMENTS_KEY]) {
|
|
52656
|
+
const globalMap = runtimeSectionMap(globalSection, key);
|
|
52657
|
+
const projectMap = runtimeSectionMap(projectSection, key);
|
|
52658
|
+
if (globalMap !== void 0 || projectMap !== void 0) provider[key] = {
|
|
52659
|
+
...globalMap,
|
|
52660
|
+
...projectMap
|
|
52661
|
+
};
|
|
52662
|
+
}
|
|
52079
52663
|
for (const key of [
|
|
52080
52664
|
TAKT_RUNTIME_DEFAULTS_KEY,
|
|
52081
52665
|
TAKT_RUNTIME_TARGETS_KEY,
|
|
@@ -52084,7 +52668,17 @@ function mergeTaktRuntimeConfigs({ project, global: globalConfig }) {
|
|
|
52084
52668
|
const value = replaced(key);
|
|
52085
52669
|
if (value !== void 0) provider[key] = value;
|
|
52086
52670
|
}
|
|
52087
|
-
|
|
52671
|
+
const enabledOf = (config) => {
|
|
52672
|
+
const companion = config?.[TAKT_RUNTIME_COMPANION_KEY];
|
|
52673
|
+
return isPlainObject$1(companion) ? companion[TAKT_RUNTIME_ENABLED_KEY] : void 0;
|
|
52674
|
+
};
|
|
52675
|
+
const globalEnabled = enabledOf(globalConfig);
|
|
52676
|
+
const projectEnabled = enabledOf(project);
|
|
52677
|
+
const companion = globalEnabled === void 0 && projectEnabled === void 0 ? void 0 : { [TAKT_RUNTIME_ENABLED_KEY]: globalEnabled !== false && projectEnabled !== false };
|
|
52678
|
+
return {
|
|
52679
|
+
[TAKT_RUNTIME_PROVIDER_KEY]: provider,
|
|
52680
|
+
...companion === void 0 ? {} : { [TAKT_RUNTIME_COMPANION_KEY]: companion }
|
|
52681
|
+
};
|
|
52088
52682
|
}
|
|
52089
52683
|
/**
|
|
52090
52684
|
* The runtime provider configuration in force for a generate run: the project
|
|
@@ -58672,8 +59266,11 @@ const CursorSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
58672
59266
|
name: zod_mini.z.string(),
|
|
58673
59267
|
description: zod_mini.z.string(),
|
|
58674
59268
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
59269
|
+
globs: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
58675
59270
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
58676
59271
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
59272
|
+
icon: zod_mini.z.optional(zod_mini.z.string()),
|
|
59273
|
+
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
58677
59274
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
58678
59275
|
});
|
|
58679
59276
|
/**
|
|
@@ -58725,10 +59322,13 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
58725
59322
|
}
|
|
58726
59323
|
toRulesyncSkill() {
|
|
58727
59324
|
const frontmatter = this.getFrontmatter();
|
|
59325
|
+
const paths = frontmatter.paths ?? frontmatter.globs;
|
|
58728
59326
|
const cursorSection = {
|
|
58729
|
-
...
|
|
59327
|
+
...paths !== void 0 && { paths },
|
|
58730
59328
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
58731
59329
|
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
59330
|
+
...frontmatter.icon !== void 0 && { icon: frontmatter.icon },
|
|
59331
|
+
...frontmatter.color !== void 0 && { color: frontmatter.color },
|
|
58732
59332
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
58733
59333
|
};
|
|
58734
59334
|
const rulesyncFrontmatter = {
|
|
@@ -58770,6 +59370,8 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
58770
59370
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
58771
59371
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
58772
59372
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
59373
|
+
...cursorSection?.icon !== void 0 && { icon: cursorSection.icon },
|
|
59374
|
+
...cursorSection?.color !== void 0 && { color: cursorSection.color },
|
|
58773
59375
|
...metadata !== void 0 && { metadata }
|
|
58774
59376
|
};
|
|
58775
59377
|
return new CursorSkill({
|
|
@@ -59753,7 +60355,8 @@ const GrokcliSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
59753
60355
|
name: zod_mini.z.string(),
|
|
59754
60356
|
description: zod_mini.z.string(),
|
|
59755
60357
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
59756
|
-
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
60358
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
60359
|
+
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
59757
60360
|
});
|
|
59758
60361
|
/**
|
|
59759
60362
|
* Represents a Grok Build skill directory.
|
|
@@ -59812,7 +60415,8 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
|
|
|
59812
60415
|
const frontmatter = this.getFrontmatter();
|
|
59813
60416
|
const grokcliSection = {
|
|
59814
60417
|
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
59815
|
-
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
60418
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
60419
|
+
...frontmatter.paths !== void 0 && { paths: frontmatter.paths }
|
|
59816
60420
|
};
|
|
59817
60421
|
const rulesyncFrontmatter = {
|
|
59818
60422
|
name: frontmatter.name,
|
|
@@ -59847,7 +60451,8 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
|
|
|
59847
60451
|
name: rulesyncFrontmatter.name,
|
|
59848
60452
|
description: rulesyncFrontmatter.description,
|
|
59849
60453
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
59850
|
-
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
60454
|
+
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
60455
|
+
...grokcliSection?.paths !== void 0 && { paths: grokcliSection.paths }
|
|
59851
60456
|
};
|
|
59852
60457
|
return new GrokcliSkill({
|
|
59853
60458
|
outputRoot,
|
|
@@ -62498,7 +63103,10 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
62498
63103
|
}
|
|
62499
63104
|
}
|
|
62500
63105
|
static getSettablePaths({ global: _global = false } = {}) {
|
|
62501
|
-
return {
|
|
63106
|
+
return {
|
|
63107
|
+
relativeDirPath: WARP_SKILLS_DIR_PATH,
|
|
63108
|
+
importOnlySkillRoots: [AGENTSMD_SKILLS_DIR_PATH]
|
|
63109
|
+
};
|
|
62502
63110
|
}
|
|
62503
63111
|
getFrontmatter() {
|
|
62504
63112
|
return WarpSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
@@ -62562,12 +63170,14 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
62562
63170
|
return targets.includes("*") || targets.includes("warp");
|
|
62563
63171
|
}
|
|
62564
63172
|
/**
|
|
62565
|
-
* Commands are emitted into
|
|
62566
|
-
* (see `WarpCommand`), so a directory matching a current rulesync
|
|
62567
|
-
* slug is owned by the commands feature: it must not be imported as
|
|
62568
|
-
* skill nor deleted as an orphan skill.
|
|
63173
|
+
* Commands are emitted into the `.warp/skills/` tree as `<slug>/SKILL.md`
|
|
63174
|
+
* (see `WarpCommand`), so a directory there matching a current rulesync
|
|
63175
|
+
* command slug is owned by the commands feature: it must not be imported as
|
|
63176
|
+
* a skill nor deleted as an orphan skill. The import-only `.agents/skills/`
|
|
63177
|
+
* root never receives commands, so a same-named skill in it stays a skill.
|
|
62569
63178
|
*/
|
|
62570
|
-
static async isDirOwned({ dirName, inputRoots }) {
|
|
63179
|
+
static async isDirOwned({ relativeDirPath, dirName, inputRoots }) {
|
|
63180
|
+
if (relativeDirPath !== WARP_SKILLS_DIR_PATH) return true;
|
|
62571
63181
|
return !await rulesyncCommandSlugExists({
|
|
62572
63182
|
inputRoots,
|
|
62573
63183
|
dirName
|
|
@@ -62780,6 +63390,25 @@ const ZedSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
62780
63390
|
description: zod_mini.z.string(),
|
|
62781
63391
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
62782
63392
|
});
|
|
63393
|
+
const ZED = "Zed";
|
|
63394
|
+
const ZED_LOAD_FAILURE = "does not load the skill otherwise";
|
|
63395
|
+
/**
|
|
63396
|
+
* Collect the rules Zed enforces on a skill's frontmatter that the loose
|
|
63397
|
+
* schema above does not — `name` rules first, then `description`. Returned as
|
|
63398
|
+
* warnings rather than thrown: the canonical skill is shared with every other
|
|
63399
|
+
* target, so a name Zed rejects must not stop the generate run for the rest.
|
|
63400
|
+
*/
|
|
63401
|
+
function collectZedSkillViolations({ name, description }) {
|
|
63402
|
+
const violations = [];
|
|
63403
|
+
if (name.length === 0) violations.push("`name` must not be empty; Zed does not load a skill without one");
|
|
63404
|
+
else violations.push(...collectSkillNameViolations({
|
|
63405
|
+
name,
|
|
63406
|
+
authority: ZED,
|
|
63407
|
+
consequence: ZED_LOAD_FAILURE
|
|
63408
|
+
}));
|
|
63409
|
+
if (description.length > 1024) violations.push(`\`description\` is ${description.length} characters; ${ZED} loads the skill but warns past ${SKILL_DESCRIPTION_MAX_LENGTH}`);
|
|
63410
|
+
return violations;
|
|
63411
|
+
}
|
|
62783
63412
|
/**
|
|
62784
63413
|
* Represents a Zed agent skill directory.
|
|
62785
63414
|
* Skills are stored under .agents/skills/ (project) or ~/.agents/skills/ (global)
|
|
@@ -62848,9 +63477,10 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
62848
63477
|
global: this.global
|
|
62849
63478
|
});
|
|
62850
63479
|
}
|
|
62851
|
-
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
63480
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false, logger }) {
|
|
62852
63481
|
const settablePaths = ZedSkill.getSettablePaths({ global });
|
|
62853
63482
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
63483
|
+
const dirName = rulesyncSkill.getDirName();
|
|
62854
63484
|
const zedSection = rulesyncFrontmatter.zed;
|
|
62855
63485
|
const resolvedDisableModelInvocation = resolveDisableModelInvocation({
|
|
62856
63486
|
rootFrontmatter: rulesyncFrontmatter,
|
|
@@ -62862,10 +63492,17 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
62862
63492
|
...zedSection,
|
|
62863
63493
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
62864
63494
|
};
|
|
63495
|
+
ZedSkill.reportSkillViolations({
|
|
63496
|
+
outputRoot,
|
|
63497
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
63498
|
+
dirName,
|
|
63499
|
+
frontmatter: zedFrontmatter,
|
|
63500
|
+
logger
|
|
63501
|
+
});
|
|
62865
63502
|
return new ZedSkill({
|
|
62866
63503
|
outputRoot,
|
|
62867
63504
|
relativeDirPath: settablePaths.relativeDirPath,
|
|
62868
|
-
dirName
|
|
63505
|
+
dirName,
|
|
62869
63506
|
frontmatter: zedFrontmatter,
|
|
62870
63507
|
body: rulesyncSkill.getBody(),
|
|
62871
63508
|
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
@@ -62873,6 +63510,18 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
62873
63510
|
global
|
|
62874
63511
|
});
|
|
62875
63512
|
}
|
|
63513
|
+
/**
|
|
63514
|
+
* Warn about every rule Zed enforces on the skill about to be written. The
|
|
63515
|
+
* reported path includes `outputRoot` so a global-scope skill points at the
|
|
63516
|
+
* file under the home directory rather than a same-named project path.
|
|
63517
|
+
*/
|
|
63518
|
+
static reportSkillViolations({ outputRoot, relativeDirPath, dirName, frontmatter, logger }) {
|
|
63519
|
+
warnSkillViolations({
|
|
63520
|
+
skillPath: (0, node_path.join)(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME),
|
|
63521
|
+
violations: collectZedSkillViolations(frontmatter),
|
|
63522
|
+
logger
|
|
63523
|
+
});
|
|
63524
|
+
}
|
|
62876
63525
|
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
62877
63526
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
62878
63527
|
return targets.includes("*") || targets.includes("zed");
|
|
@@ -65482,11 +66131,17 @@ var CopilotSubagent = class CopilotSubagent extends ToolSubagent {
|
|
|
65482
66131
|
*
|
|
65483
66132
|
* Reference: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli
|
|
65484
66133
|
* https://docs.github.com/en/copilot/reference/custom-agents-configuration
|
|
66134
|
+
* https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference
|
|
65485
66135
|
*
|
|
65486
66136
|
* Per the configuration docs:
|
|
65487
66137
|
* - `description` is required.
|
|
65488
66138
|
* - `name`, `target`, `tools`, `model`, `disable-model-invocation`,
|
|
65489
66139
|
* `user-invocable`, `mcp-servers`, and `metadata` are optional.
|
|
66140
|
+
* - The CLI command reference adds `models` (authored models in priority
|
|
66141
|
+
* order, overriding `model` when both are set) and `modelPolicy`
|
|
66142
|
+
* (`preferred` lets a `subagents` settings override or the `/subagents`
|
|
66143
|
+
* picker replace the model; `required` locks dispatch to the authored
|
|
66144
|
+
* list). CLI v1.0.81+; the shared reference page still lists `model` only.
|
|
65490
66145
|
*
|
|
65491
66146
|
* `looseObject` is used so that future additional fields are passed through
|
|
65492
66147
|
* unchanged (consistent with the rest of rulesync's frontmatter schemas).
|
|
@@ -65497,6 +66152,8 @@ const CopilotCliSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
65497
66152
|
target: zod_mini.z.optional(zod_mini.z.string()),
|
|
65498
66153
|
tools: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
65499
66154
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
66155
|
+
models: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
66156
|
+
modelPolicy: zod_mini.z.optional(zod_mini.z.enum(["preferred", "required"])),
|
|
65500
66157
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
65501
66158
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
65502
66159
|
"mcp-servers": zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.unknown())),
|
|
@@ -69873,16 +70530,131 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
69873
70530
|
}
|
|
69874
70531
|
};
|
|
69875
70532
|
//#endregion
|
|
70533
|
+
//#region src/utils/brace-aware-list.ts
|
|
70534
|
+
/**
|
|
70535
|
+
* Splits a comma-separated pattern list on the commas that separate its
|
|
70536
|
+
* entries, leaving the commas inside a brace group alone: in
|
|
70537
|
+
* `{src,lib}/**\/*.ts, tests/**\/*.test.ts` the first comma belongs to the
|
|
70538
|
+
* brace expansion and the second separates the two globs. Entries are trimmed;
|
|
70539
|
+
* empty entries are dropped.
|
|
70540
|
+
*
|
|
70541
|
+
* @example
|
|
70542
|
+
* splitBraceAwareList("src/**\/*.{ts,tsx}, docs/**")
|
|
70543
|
+
* // => ["src/**\/*.{ts,tsx}", "docs/**"]
|
|
70544
|
+
*/
|
|
70545
|
+
const splitBraceAwareList = (value) => {
|
|
70546
|
+
const entries = [];
|
|
70547
|
+
let current = "";
|
|
70548
|
+
let braceDepth = 0;
|
|
70549
|
+
for (const char of value) {
|
|
70550
|
+
if (char === "{") braceDepth += 1;
|
|
70551
|
+
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
70552
|
+
else if (char === "," && braceDepth === 0) {
|
|
70553
|
+
entries.push(current);
|
|
70554
|
+
current = "";
|
|
70555
|
+
continue;
|
|
70556
|
+
}
|
|
70557
|
+
current += char;
|
|
70558
|
+
}
|
|
70559
|
+
entries.push(current);
|
|
70560
|
+
return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
70561
|
+
};
|
|
70562
|
+
/**
|
|
70563
|
+
* Upper bound on the patterns one glob may expand to; past it the glob is
|
|
70564
|
+
* returned verbatim, since `{a,b}` repeated k times yields 2^k branches.
|
|
70565
|
+
*/
|
|
70566
|
+
const MAX_BRACE_EXPANSIONS = 256;
|
|
70567
|
+
/** Matches an innermost brace group with at least one comma: `{a,b}`, not `{a}` or `{a,{b,c}}`. */
|
|
70568
|
+
const INNERMOST_BRACE_GROUP_REGEX = /\{([^{},]*(?:,[^{},]*)+)\}/;
|
|
70569
|
+
/**
|
|
70570
|
+
* Expands the comma alternations of a glob (`a.{ts,tsx}` → `a.ts`, `a.tsx`)
|
|
70571
|
+
* into one pattern per combination, innermost group first, so the result can
|
|
70572
|
+
* be handed to a consumer that splits its pattern list on bare commas. A glob
|
|
70573
|
+
* without a brace alternation is returned as is; a brace group without a
|
|
70574
|
+
* comma (e.g. `{a}`) is left untouched. A glob whose expansion would exceed
|
|
70575
|
+
* `MAX_BRACE_EXPANSIONS` patterns is returned as is as well.
|
|
70576
|
+
*
|
|
70577
|
+
* The cap is enforced on the work list itself rather than by counting the
|
|
70578
|
+
* groups up front: a nested group such as `{{a,b},c}` is rewritten into one
|
|
70579
|
+
* new group per inner alternative, so the number of patterns in flight is the
|
|
70580
|
+
* only bound that holds for arbitrary nesting.
|
|
70581
|
+
*
|
|
70582
|
+
* @example
|
|
70583
|
+
* expandBraceAlternations("src/**\/*.{ts,tsx}")
|
|
70584
|
+
* // => ["src/**\/*.ts", "src/**\/*.tsx"]
|
|
70585
|
+
*/
|
|
70586
|
+
const expandBraceAlternations = (glob) => {
|
|
70587
|
+
let pending = [glob];
|
|
70588
|
+
for (;;) {
|
|
70589
|
+
const next = [];
|
|
70590
|
+
let expanded = false;
|
|
70591
|
+
for (const pattern of pending) {
|
|
70592
|
+
const match = INNERMOST_BRACE_GROUP_REGEX.exec(pattern);
|
|
70593
|
+
if (match === null) {
|
|
70594
|
+
next.push(pattern);
|
|
70595
|
+
continue;
|
|
70596
|
+
}
|
|
70597
|
+
expanded = true;
|
|
70598
|
+
const prefix = pattern.slice(0, match.index);
|
|
70599
|
+
const suffix = pattern.slice(match.index + match[0].length);
|
|
70600
|
+
for (const alternative of (match[1] ?? "").split(",")) next.push(`${prefix}${alternative}${suffix}`);
|
|
70601
|
+
if (next.length > MAX_BRACE_EXPANSIONS) return [glob];
|
|
70602
|
+
}
|
|
70603
|
+
if (!expanded) return [...new Set(next)];
|
|
70604
|
+
pending = next;
|
|
70605
|
+
}
|
|
70606
|
+
};
|
|
70607
|
+
//#endregion
|
|
69876
70608
|
//#region src/features/rules/aiassistant-rule.ts
|
|
69877
70609
|
/**
|
|
70610
|
+
* Rule types AI Assistant recognizes on the `apply:` metadata line. The values
|
|
70611
|
+
* are matched case-sensitively by the plugin, so they are emitted verbatim.
|
|
70612
|
+
* Any other value is carried through untouched (the plugin treats it as Off
|
|
70613
|
+
* today; a future build may recognize it).
|
|
70614
|
+
*/
|
|
70615
|
+
const AIASSISTANT_APPLY_VALUES = [
|
|
70616
|
+
"always",
|
|
70617
|
+
"manually",
|
|
70618
|
+
"by model decision",
|
|
70619
|
+
"by file patterns",
|
|
70620
|
+
"off"
|
|
70621
|
+
];
|
|
70622
|
+
const isAiassistantApply = (value) => AIASSISTANT_APPLY_VALUES.includes(value);
|
|
70623
|
+
/**
|
|
70624
|
+
* Globs that match the whole project and so say nothing beyond "always".
|
|
70625
|
+
*/
|
|
70626
|
+
const UNIVERSAL_GLOBS$2 = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
70627
|
+
const METADATA_DELIMITER = "---";
|
|
70628
|
+
/**
|
|
70629
|
+
* Splits text into lines that keep their own line terminator, so the body can
|
|
70630
|
+
* be sliced back out of the file byte-for-byte. Lone `\r` counts as a break,
|
|
70631
|
+
* matching Kotlin's `lines()`, which the plugin uses on the block.
|
|
70632
|
+
*/
|
|
70633
|
+
const LINE_SPLIT_REGEX = /(?<=\r\n|\r(?!\n)|(?<!\r)\n)/;
|
|
70634
|
+
/**
|
|
70635
|
+
* The block is line-based, so a value must not carry a line break: one would
|
|
70636
|
+
* start a new `<field>: <value>` line and could override `apply:`.
|
|
70637
|
+
*/
|
|
70638
|
+
const flattenValue = (value) => value.split(/\r\n|\r|\n/).map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
|
|
70639
|
+
/**
|
|
69878
70640
|
* Rule generator for JetBrains AI Assistant.
|
|
69879
70641
|
*
|
|
69880
70642
|
* AI Assistant reads project rules as flat Markdown files in
|
|
69881
|
-
* `.aiassistant/rules/*.md` (one file per rule, identified by filename).
|
|
69882
|
-
*
|
|
69883
|
-
*
|
|
69884
|
-
*
|
|
69885
|
-
*
|
|
70643
|
+
* `.aiassistant/rules/*.md` (one file per rule, identified by filename). There
|
|
70644
|
+
* is no special root file (unlike Junie's `guidelines.md`), so every rule —
|
|
70645
|
+
* root or non-root — is written as its own file under `rules/`.
|
|
70646
|
+
*
|
|
70647
|
+
* The rule type (Always / Manually / By model decision / By file patterns /
|
|
70648
|
+
* Off) is stored in the file itself as a leading `---` metadata block that the
|
|
70649
|
+
* IDE's "Rule type" selector edits in place. The block is not YAML: each line
|
|
70650
|
+
* is `<field>: <value>` split on the first colon, and `patterns` is a
|
|
70651
|
+
* comma-separated glob list, so it is written and parsed by hand here. A file
|
|
70652
|
+
* without an `apply:` line is treated as Off at retrieval time (it is never
|
|
70653
|
+
* attached automatically), which is why every generated file carries one.
|
|
70654
|
+
*
|
|
70655
|
+
* Field names and values come from the plugin (`AiRulesService`,
|
|
70656
|
+
* `AiRulesLLMBundle.properties`; verified on build 262.10968.75) — the docs
|
|
70657
|
+
* page describes the rule type only as an IDE setting.
|
|
69886
70658
|
*
|
|
69887
70659
|
* AI Assistant and Junie are different JetBrains products (real-time assistance
|
|
69888
70660
|
* vs. autonomous agent) with different layouts, so this is a separate target.
|
|
@@ -69890,18 +70662,154 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
69890
70662
|
* @see https://www.jetbrains.com/help/ai-assistant/configure-project-rules.html
|
|
69891
70663
|
*/
|
|
69892
70664
|
var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
70665
|
+
body;
|
|
70666
|
+
metadata;
|
|
70667
|
+
constructor({ body, metadata, ...rest }) {
|
|
70668
|
+
super({
|
|
70669
|
+
...rest,
|
|
70670
|
+
fileContent: AiassistantRule.buildFileContent({
|
|
70671
|
+
body,
|
|
70672
|
+
metadata
|
|
70673
|
+
})
|
|
70674
|
+
});
|
|
70675
|
+
this.body = body;
|
|
70676
|
+
this.metadata = metadata;
|
|
70677
|
+
}
|
|
69893
70678
|
static getSettablePaths(_options = {}) {
|
|
69894
70679
|
return { nonRoot: { relativeDirPath: AIASSISTANT_RULES_DIR_PATH } };
|
|
69895
70680
|
}
|
|
70681
|
+
getBody() {
|
|
70682
|
+
return this.body;
|
|
70683
|
+
}
|
|
70684
|
+
getMetadata() {
|
|
70685
|
+
return this.metadata;
|
|
70686
|
+
}
|
|
70687
|
+
/**
|
|
70688
|
+
* Serialize the metadata block the way the IDE writes it
|
|
70689
|
+
* (`---\n<field>: <value>\n---`), followed by the body.
|
|
70690
|
+
*/
|
|
70691
|
+
static buildFileContent({ body, metadata }) {
|
|
70692
|
+
if (metadata === void 0) return body;
|
|
70693
|
+
const lines = [`apply: ${metadata.apply}`];
|
|
70694
|
+
if (metadata.apply === "by model decision" && metadata.instructions) lines.push(`instructions: ${flattenValue(metadata.instructions)}`);
|
|
70695
|
+
if (metadata.apply === "by file patterns" && metadata.patterns && metadata.patterns.length > 0) lines.push(`patterns: ${metadata.patterns.map(flattenValue).join(", ")}`);
|
|
70696
|
+
return `${METADATA_DELIMITER}\n${lines.join("\n")}\n${METADATA_DELIMITER}\n\n${body}`;
|
|
70697
|
+
}
|
|
70698
|
+
/**
|
|
70699
|
+
* Split a rule file into its metadata block and body. The plugin locates the
|
|
70700
|
+
* block with `^---\s*([\s\S]*?)---\s*` (anchored at the very start of the
|
|
70701
|
+
* file); this reads it line by line instead — a delimiter is a line that is
|
|
70702
|
+
* exactly `---` — which is linear on any input and tolerates a BOM or blank
|
|
70703
|
+
* lines before the block. Each line is split on its first colon and trimmed;
|
|
70704
|
+
* unknown fields are ignored; a missing block or `apply:` line yields no
|
|
70705
|
+
* metadata, while an unrecognized `apply:` value is kept verbatim.
|
|
70706
|
+
*/
|
|
70707
|
+
static parseFileContent(fileContent) {
|
|
70708
|
+
const content = fileContent.replace(/^\uFEFF/, "").trimStart();
|
|
70709
|
+
const lines = content.split(LINE_SPLIT_REGEX);
|
|
70710
|
+
const closingIndex = lines[0]?.trim() === METADATA_DELIMITER ? lines.findIndex((line, index) => index > 0 && line.trim() === METADATA_DELIMITER) : -1;
|
|
70711
|
+
if (closingIndex === -1) return {
|
|
70712
|
+
metadata: void 0,
|
|
70713
|
+
body: content.trim()
|
|
70714
|
+
};
|
|
70715
|
+
const body = lines.slice(closingIndex + 1).join("").trim();
|
|
70716
|
+
const fields = /* @__PURE__ */ new Map();
|
|
70717
|
+
for (const line of lines.slice(1, closingIndex)) {
|
|
70718
|
+
const separatorIndex = line.indexOf(":");
|
|
70719
|
+
if (separatorIndex === -1) continue;
|
|
70720
|
+
fields.set(line.slice(0, separatorIndex).trim(), line.slice(separatorIndex + 1).trim());
|
|
70721
|
+
}
|
|
70722
|
+
const apply = fields.get("apply");
|
|
70723
|
+
if (apply === void 0 || apply.length === 0) return {
|
|
70724
|
+
metadata: void 0,
|
|
70725
|
+
body
|
|
70726
|
+
};
|
|
70727
|
+
const instructions = fields.get("instructions");
|
|
70728
|
+
const patterns = fields.get("patterns");
|
|
70729
|
+
return {
|
|
70730
|
+
metadata: {
|
|
70731
|
+
apply,
|
|
70732
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
70733
|
+
...apply === "by file patterns" && patterns && { patterns: splitBraceAwareList(patterns) }
|
|
70734
|
+
},
|
|
70735
|
+
body
|
|
70736
|
+
};
|
|
70737
|
+
}
|
|
70738
|
+
/**
|
|
70739
|
+
* Derive the metadata block from the canonical frontmatter, mirroring the
|
|
70740
|
+
* Cursor mapping: an explicit `aiassistant.apply` wins; otherwise specific
|
|
70741
|
+
* `globs` become `by file patterns`, a `description` on a rule with no
|
|
70742
|
+
* `globs` at all becomes the `by model decision` condition, and everything
|
|
70743
|
+
* else — the root rule, universal globs (which already say "every file",
|
|
70744
|
+
* whatever the description), bare rules — is `always`.
|
|
70745
|
+
*
|
|
70746
|
+
* The plugin splits `patterns` on every comma, so a brace alternation such
|
|
70747
|
+
* as `*.{ts,tsx}` is expanded into one glob per branch before it is written.
|
|
70748
|
+
*/
|
|
70749
|
+
static buildMetadata(frontmatter) {
|
|
70750
|
+
const globs = (frontmatter.globs ?? []).map((glob) => flattenValue(glob)).filter((glob) => glob.length > 0).flatMap((glob) => expandBraceAlternations(glob));
|
|
70751
|
+
const hasUniversalGlob = globs.some((glob) => UNIVERSAL_GLOBS$2.has(glob));
|
|
70752
|
+
const instructions = frontmatter.description && flattenValue(frontmatter.description);
|
|
70753
|
+
const explicitApply = frontmatter.aiassistant?.apply && flattenValue(frontmatter.aiassistant.apply);
|
|
70754
|
+
const apply = explicitApply ? explicitApply : frontmatter.root !== true && globs.length > 0 && !hasUniversalGlob ? "by file patterns" : frontmatter.root !== true && globs.length === 0 && instructions ? "by model decision" : "always";
|
|
70755
|
+
const patterns = globs;
|
|
70756
|
+
return {
|
|
70757
|
+
apply,
|
|
70758
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
70759
|
+
...apply === "by file patterns" && patterns.length > 0 && { patterns }
|
|
70760
|
+
};
|
|
70761
|
+
}
|
|
70762
|
+
/**
|
|
70763
|
+
* Warn, at generate time, about a block the plugin cannot act on as the
|
|
70764
|
+
* author presumably intended.
|
|
70765
|
+
*/
|
|
70766
|
+
static warnAboutMetadata({ metadata, relativeFilePath }) {
|
|
70767
|
+
const { apply, instructions, patterns = [] } = metadata;
|
|
70768
|
+
if (!isAiassistantApply(apply)) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply "${apply}" is not one of ${AIASSISTANT_APPLY_VALUES.join(", ")}; it is written as is, and the current AI Assistant treats such a rule as Off.`);
|
|
70769
|
+
if (apply === "by file patterns" && patterns.length === 0) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply is "by file patterns" but the rule has no globs, so AI Assistant will never attach it.`);
|
|
70770
|
+
if (apply === "by model decision" && !instructions) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply is "by model decision" but the rule has no description, so AI Assistant has no condition to decide on.`);
|
|
70771
|
+
const commaGlob = patterns.find((glob) => glob.includes(","));
|
|
70772
|
+
if (commaGlob !== void 0) warnWithFallback(void 0, `${relativeFilePath}: the glob "${commaGlob}" contains a comma, which AI Assistant reads as a pattern separator.`);
|
|
70773
|
+
if ([
|
|
70774
|
+
apply,
|
|
70775
|
+
instructions ?? "",
|
|
70776
|
+
...patterns
|
|
70777
|
+
].some((value) => value.includes("---"))) warnWithFallback(void 0, `${relativeFilePath}: a metadata value contains "---", which AI Assistant reads as the end of the metadata block.`);
|
|
70778
|
+
}
|
|
69896
70779
|
toRulesyncRule() {
|
|
69897
|
-
|
|
70780
|
+
const metadata = this.metadata;
|
|
70781
|
+
const globs = metadata?.apply === "by file patterns" ? metadata.patterns ?? [] : metadata?.apply === "always" ? ["**/*"] : [];
|
|
70782
|
+
const frontmatter = {
|
|
70783
|
+
root: false,
|
|
70784
|
+
targets: ["*"],
|
|
70785
|
+
description: metadata?.apply === "by model decision" ? metadata.instructions : void 0,
|
|
70786
|
+
globs
|
|
70787
|
+
};
|
|
70788
|
+
const derivedApply = metadata === void 0 ? void 0 : AiassistantRule.buildMetadata(frontmatter).apply;
|
|
70789
|
+
const apply = metadata !== void 0 && derivedApply !== metadata.apply ? metadata.apply : void 0;
|
|
70790
|
+
return new RulesyncRule({
|
|
70791
|
+
outputRoot: process.cwd(),
|
|
70792
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
70793
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
70794
|
+
frontmatter: {
|
|
70795
|
+
...frontmatter,
|
|
70796
|
+
...apply !== void 0 && { aiassistant: { apply } }
|
|
70797
|
+
},
|
|
70798
|
+
body: this.body
|
|
70799
|
+
});
|
|
69898
70800
|
}
|
|
69899
70801
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
70802
|
+
const metadata = this.buildMetadata(rulesyncRule.getFrontmatter());
|
|
70803
|
+
this.warnAboutMetadata({
|
|
70804
|
+
metadata,
|
|
70805
|
+
relativeFilePath: rulesyncRule.getRelativeFilePath()
|
|
70806
|
+
});
|
|
69900
70807
|
return new AiassistantRule({
|
|
69901
70808
|
outputRoot,
|
|
69902
70809
|
relativeDirPath: this.getSettablePaths().nonRoot.relativeDirPath,
|
|
69903
70810
|
relativeFilePath: rulesyncRule.getRelativeFilePath(),
|
|
69904
|
-
|
|
70811
|
+
body: rulesyncRule.getBody(),
|
|
70812
|
+
metadata,
|
|
69905
70813
|
validate,
|
|
69906
70814
|
root: false
|
|
69907
70815
|
});
|
|
@@ -69909,11 +70817,13 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
69909
70817
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
69910
70818
|
const relativeDirPath = this.getSettablePaths().nonRoot.relativeDirPath;
|
|
69911
70819
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
70820
|
+
const { metadata, body } = this.parseFileContent(fileContent);
|
|
69912
70821
|
return new AiassistantRule({
|
|
69913
70822
|
outputRoot,
|
|
69914
70823
|
relativeDirPath,
|
|
69915
70824
|
relativeFilePath,
|
|
69916
|
-
|
|
70825
|
+
body,
|
|
70826
|
+
metadata,
|
|
69917
70827
|
validate,
|
|
69918
70828
|
root: false
|
|
69919
70829
|
});
|
|
@@ -69923,7 +70833,7 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
69923
70833
|
outputRoot,
|
|
69924
70834
|
relativeDirPath,
|
|
69925
70835
|
relativeFilePath,
|
|
69926
|
-
|
|
70836
|
+
body: "",
|
|
69927
70837
|
validate: false,
|
|
69928
70838
|
root: false
|
|
69929
70839
|
});
|
|
@@ -71480,40 +72390,14 @@ const CodebuddyRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
71480
72390
|
enabled: zod_mini.z.optional(zod_mini.z.boolean())
|
|
71481
72391
|
});
|
|
71482
72392
|
/**
|
|
71483
|
-
* Splits a scalar `paths` value on the commas that separate patterns, leaving
|
|
71484
|
-
* the commas inside a brace group alone: CodeBuddy's memory docs say "You can
|
|
71485
|
-
* also combine multiple patterns with commas" and give
|
|
71486
|
-
* `paths: {src,lib}/**\/*.ts, tests/**\/*.test.ts` as the example, where the
|
|
71487
|
-
* first comma belongs to the brace expansion and the second separates the two
|
|
71488
|
-
* globs. Only a scalar is split — the docs describe the comma form for the
|
|
71489
|
-
* string shape, and a list already separates its patterns.
|
|
71490
|
-
*/
|
|
71491
|
-
function splitCodebuddyPathsScalar(paths) {
|
|
71492
|
-
const patterns = [];
|
|
71493
|
-
let current = "";
|
|
71494
|
-
let braceDepth = 0;
|
|
71495
|
-
for (const char of paths) {
|
|
71496
|
-
if (char === "{") braceDepth += 1;
|
|
71497
|
-
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
71498
|
-
else if (char === "," && braceDepth === 0) {
|
|
71499
|
-
patterns.push(current);
|
|
71500
|
-
current = "";
|
|
71501
|
-
continue;
|
|
71502
|
-
}
|
|
71503
|
-
current += char;
|
|
71504
|
-
}
|
|
71505
|
-
patterns.push(current);
|
|
71506
|
-
return patterns.map((pattern) => pattern.trim());
|
|
71507
|
-
}
|
|
71508
|
-
/**
|
|
71509
72393
|
* Normalizes the documented `string` / `string[]` shapes of `paths` to the
|
|
71510
72394
|
* list form the rest of the adapter works with, splitting a comma-separated
|
|
71511
|
-
* scalar into its patterns (see `
|
|
72395
|
+
* scalar into its patterns (see `splitBraceAwareList`). An empty list
|
|
71512
72396
|
* and an empty string both mean "no paths".
|
|
71513
72397
|
*/
|
|
71514
72398
|
function normalizeCodebuddyPaths(paths) {
|
|
71515
72399
|
if (paths === void 0) return;
|
|
71516
|
-
const list = (typeof paths === "string" ?
|
|
72400
|
+
const list = (typeof paths === "string" ? splitBraceAwareList(paths) : paths).filter((path) => path.trim() !== "");
|
|
71517
72401
|
return list.length > 0 ? list : void 0;
|
|
71518
72402
|
}
|
|
71519
72403
|
/**
|