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
|
@@ -4053,24 +4053,60 @@ const CONTROL_CHARS = [
|
|
|
4053
4053
|
const hasControlChars = (val) => CONTROL_CHARS.some((char) => val.includes(char));
|
|
4054
4054
|
const safeString = z.pipe(z.string(), z.custom((val) => typeof val === "string" && !hasControlChars(val), "must not contain newline, carriage return, or NUL characters"));
|
|
4055
4055
|
/**
|
|
4056
|
+
* All canonical hook types — the union of the `type` values accepted by every
|
|
4057
|
+
* supported tool:
|
|
4058
|
+
* - `command` — universal (Claude Code, Codex CLI, Qwen Code, Cursor, …)
|
|
4059
|
+
* - `prompt` — Claude Code, Codex CLI, Qwen Code, Copilot CLI, Cursor, Devin
|
|
4060
|
+
* - `http` — Claude Code, Qwen Code, Copilot CLI, Grok CLI
|
|
4061
|
+
* - `agent` — Claude Code, Codex CLI
|
|
4062
|
+
* - `mcp_tool` — Claude Code
|
|
4063
|
+
* - `function` — Qwen Code (internal Skill-system executor)
|
|
4064
|
+
*/
|
|
4065
|
+
const HOOK_TYPES = [
|
|
4066
|
+
"command",
|
|
4067
|
+
"prompt",
|
|
4068
|
+
"http",
|
|
4069
|
+
"agent",
|
|
4070
|
+
"mcp_tool",
|
|
4071
|
+
"function"
|
|
4072
|
+
];
|
|
4073
|
+
/**
|
|
4074
|
+
* One choice offered by a Kiro `confirm` prompt: `run` decides whether picking
|
|
4075
|
+
* it executes the hook command. The prompt text is written back into JSON,
|
|
4076
|
+
* never embedded in generated code, so it is a plain string.
|
|
4077
|
+
*/
|
|
4078
|
+
const HookConfirmOptionSchema = z.looseObject({
|
|
4079
|
+
id: z.string(),
|
|
4080
|
+
label: z.string(),
|
|
4081
|
+
run: z.boolean()
|
|
4082
|
+
});
|
|
4083
|
+
/**
|
|
4084
|
+
* Kiro's confirmation prompt shown before a `Stop`-trigger command hook runs.
|
|
4085
|
+
* `confirmCommand` (optional, inside the block) names a command whose JSON
|
|
4086
|
+
* stdout drives the prompt at run time: `{ "skip": true }` suppresses it, a
|
|
4087
|
+
* replacement `question` / `options` object overrides the static prompt, and
|
|
4088
|
+
* on failure or timeout the static prompt is used. Stored verbatim: Kiro is
|
|
4089
|
+
* the only consumer, and it owns the shape.
|
|
4090
|
+
* https://kiro.dev/docs/hooks/
|
|
4091
|
+
*/
|
|
4092
|
+
const HookConfirmSchema = z.looseObject({
|
|
4093
|
+
question: z.string(),
|
|
4094
|
+
options: z.array(HookConfirmOptionSchema),
|
|
4095
|
+
confirmCommand: z.optional(safeString)
|
|
4096
|
+
});
|
|
4097
|
+
/**
|
|
4056
4098
|
* Canonical hook definition.
|
|
4057
4099
|
* Used in .rulesync/hooks.jsonc and mapped to tool-specific formats.
|
|
4058
4100
|
*/
|
|
4059
4101
|
const HookDefinitionSchema = z.looseObject({
|
|
4060
4102
|
command: z.optional(safeString),
|
|
4061
|
-
type: z.optional(z.enum(
|
|
4062
|
-
"command",
|
|
4063
|
-
"prompt",
|
|
4064
|
-
"http",
|
|
4065
|
-
"agent",
|
|
4066
|
-
"mcp_tool",
|
|
4067
|
-
"function"
|
|
4068
|
-
])),
|
|
4103
|
+
type: z.optional(z.enum(HOOK_TYPES)),
|
|
4069
4104
|
url: z.optional(safeString),
|
|
4070
4105
|
timeout: z.optional(z.number()),
|
|
4071
4106
|
cacheTtl: z.optional(z.number().check(nonnegative())),
|
|
4072
4107
|
matcher: z.optional(safeString),
|
|
4073
4108
|
enabled: z.optional(z.boolean()),
|
|
4109
|
+
confirm: z.optional(HookConfirmSchema),
|
|
4074
4110
|
prompt: z.optional(safeString),
|
|
4075
4111
|
loop_limit: z.optional(z.nullable(z.number())),
|
|
4076
4112
|
name: z.optional(safeString),
|
|
@@ -4348,9 +4384,13 @@ const AMP_HOOK_EVENTS = [
|
|
|
4348
4384
|
* the platform the other one owns — see `generateClineHookScript` and
|
|
4349
4385
|
* `generateClineHookPowerShellScript`.
|
|
4350
4386
|
*
|
|
4351
|
-
* `
|
|
4352
|
-
*
|
|
4353
|
-
*
|
|
4387
|
+
* `TaskCancel` is the shape of canonical `stopCancelled` (as Grok CLI's
|
|
4388
|
+
* `StopCancelled` and Codex CLI's `Interrupt` are): both runtimes fire it
|
|
4389
|
+
* *instead of* `TaskComplete` when the run ends aborted — the VS Code
|
|
4390
|
+
* extension from `afterRun` when the result status is `aborted`, the SDK/CLI
|
|
4391
|
+
* as `agent_abort` — and neither lets the script cancel anything. `TaskResume`
|
|
4392
|
+
* has no canonical counterpart and stays unmapped rather than being
|
|
4393
|
+
* approximated by `sessionStart`, whose semantics differ.
|
|
4354
4394
|
*
|
|
4355
4395
|
* @see https://github.com/cline/cline/blob/main/apps/vscode/src/core/hooks/utils.ts
|
|
4356
4396
|
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/hooks/hook-file-config.ts
|
|
@@ -4364,7 +4404,8 @@ const CLINE_HOOK_EVENTS = [
|
|
|
4364
4404
|
"preCompact",
|
|
4365
4405
|
"notification",
|
|
4366
4406
|
"taskCompleted",
|
|
4367
|
-
"afterError"
|
|
4407
|
+
"afterError",
|
|
4408
|
+
"stopCancelled"
|
|
4368
4409
|
];
|
|
4369
4410
|
/**
|
|
4370
4411
|
* Hook events supported by GitHub Copilot (cloud coding agent).
|
|
@@ -4682,23 +4723,32 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
4682
4723
|
* Hook events supported by Reasonix.
|
|
4683
4724
|
*
|
|
4684
4725
|
* Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
|
|
4685
|
-
* (global)
|
|
4686
|
-
* `
|
|
4687
|
-
* `
|
|
4726
|
+
* (global) accepts the thirteen events declared in `internal/hook/hook.go`
|
|
4727
|
+
* (`var Events`): `PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
|
|
4728
|
+
* `PermissionRequest`, `UserPromptSubmit`, `Stop`, `StopFailure`,
|
|
4729
|
+
* `PostLLMCall`, `SessionStart`, `SessionEnd`, `SubagentStop`, `Notification`,
|
|
4730
|
+
* `PreCompact`. The zh-CN desktop hooks page documents only ten of them, so the
|
|
4731
|
+
* source file is the authoritative list. All thirteen have a clean canonical
|
|
4688
4732
|
* equivalent and are mapped: `PreToolUse`, `PostToolUse`,
|
|
4689
|
-
* `
|
|
4690
|
-
* `
|
|
4691
|
-
* `
|
|
4692
|
-
* `
|
|
4693
|
-
* `
|
|
4694
|
-
*
|
|
4733
|
+
* `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit` ←
|
|
4734
|
+
* `beforeSubmitPrompt`, `Stop`, `StopFailure`, `SessionStart`, `SessionEnd`,
|
|
4735
|
+
* `SubagentStop`, `PostLLMCall` ← `postModelInvocation`, `Notification` ←
|
|
4736
|
+
* `notification`, and `PreCompact` ← `preCompact`.
|
|
4737
|
+
* `match` (Reasonix's matcher field name) is honored on the four tool-scoped
|
|
4738
|
+
* events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
|
|
4739
|
+
* `PermissionRequest` — `UsesToolMatcher` in `internal/hook/inspect.go`) and
|
|
4740
|
+
* ignored everywhere else.
|
|
4741
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/hook/hook.go
|
|
4695
4742
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
4696
4743
|
*/
|
|
4697
4744
|
const REASONIX_HOOK_EVENTS = [
|
|
4698
4745
|
"preToolUse",
|
|
4699
4746
|
"postToolUse",
|
|
4747
|
+
"postToolUseFailure",
|
|
4748
|
+
"permissionRequest",
|
|
4700
4749
|
"beforeSubmitPrompt",
|
|
4701
4750
|
"stop",
|
|
4751
|
+
"stopFailure",
|
|
4702
4752
|
"sessionStart",
|
|
4703
4753
|
"sessionEnd",
|
|
4704
4754
|
"subagentStop",
|
|
@@ -4744,8 +4794,8 @@ const GROKCLI_HOOK_EVENTS = [
|
|
|
4744
4794
|
* added in 0.32.0 (`TurnStarted`, `UserPromptQueued`, `TaskStarted`,
|
|
4745
4795
|
* `SessionHeartbeat`), none of which is mapped onto a canonical rulesync event
|
|
4746
4796
|
* here. `Interrupt` (fires instead of `Stop` when the user interrupts a turn)
|
|
4747
|
-
* does have a canonical shape — `stopCancelled`, which Grok CLI
|
|
4748
|
-
* map — but Kimi keeps it native-only for now, since an existing `kimi-code`
|
|
4797
|
+
* does have a canonical shape — `stopCancelled`, which Grok CLI, Codex CLI and
|
|
4798
|
+
* Cline map — but Kimi keeps it native-only for now, since an existing `kimi-code`
|
|
4749
4799
|
* override addressing it by name would otherwise double up with a canonical
|
|
4750
4800
|
* `stopCancelled` block; folding it in is a follow-up. All six are listed in
|
|
4751
4801
|
* `KIMI_CODE_NATIVE_HOOK_EVENTS` so a per-tool `kimi-code` override can
|
|
@@ -5040,8 +5090,8 @@ const CONTINUE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
5040
5090
|
/**
|
|
5041
5091
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
5042
5092
|
*
|
|
5043
|
-
* Hermes validates hook events against a fixed `VALID_HOOKS` set —
|
|
5044
|
-
* of v0.
|
|
5093
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 39 entries as
|
|
5094
|
+
* of v0.21.3 (`v2026.9.14`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
|
|
5045
5095
|
* full list. Only the events with a clean 1:1 canonical equivalent are mapped
|
|
5046
5096
|
* here. All other native events round-trip through `hermesagent.hooks`.
|
|
5047
5097
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
@@ -5065,11 +5115,13 @@ const HERMESAGENT_HOOK_EVENTS = [
|
|
|
5065
5115
|
* observers, `gateway_platform_event` and `transform_api_error_classification`.
|
|
5066
5116
|
* Shell hooks are gated on the same set.
|
|
5067
5117
|
*
|
|
5068
|
-
* Verified against the tag `v2026.
|
|
5069
|
-
* entries, grown from 23 at `v2026.8.3` (v0.20.0)
|
|
5070
|
-
*
|
|
5118
|
+
* Verified against the tag `v2026.9.14` (v0.21.3): `VALID_HOOKS` holds 39
|
|
5119
|
+
* entries, grown from 23 at `v2026.8.3` (v0.20.0) and 37 at `v2026.8.16`
|
|
5120
|
+
* (v0.20.2) — v0.21 added `agent_loop_stopped` and `on_room_member_activity`.
|
|
5121
|
+
* This list holds 38 of them — see
|
|
5122
|
+
* {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
|
|
5071
5123
|
*
|
|
5072
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.
|
|
5124
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/hermes_cli/plugins.py
|
|
5073
5125
|
*/
|
|
5074
5126
|
const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
5075
5127
|
"pre_tool_call",
|
|
@@ -5095,8 +5147,10 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
5095
5147
|
"subagent_start",
|
|
5096
5148
|
"subagent_stop",
|
|
5097
5149
|
"pre_gateway_dispatch",
|
|
5150
|
+
"agent_loop_stopped",
|
|
5098
5151
|
"pre_approval_request",
|
|
5099
5152
|
"post_approval_response",
|
|
5153
|
+
"on_room_member_activity",
|
|
5100
5154
|
"pre_transcription",
|
|
5101
5155
|
"kanban_task_claimed",
|
|
5102
5156
|
"kanban_task_completed",
|
|
@@ -5119,8 +5173,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
|
|
|
5119
5173
|
* rulesync only ever writes shell hooks, so these are excluded from
|
|
5120
5174
|
* {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
|
|
5121
5175
|
*
|
|
5122
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.
|
|
5123
|
-
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.
|
|
5176
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
|
|
5177
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/agent/shell_hooks.py — `_parse_hooks_block`
|
|
5124
5178
|
*/
|
|
5125
5179
|
const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
|
|
5126
5180
|
/**
|
|
@@ -5421,7 +5475,8 @@ const CANONICAL_TO_CLINE_EVENT_NAMES = {
|
|
|
5421
5475
|
preCompact: "PreCompact",
|
|
5422
5476
|
notification: "Notification",
|
|
5423
5477
|
taskCompleted: "TaskComplete",
|
|
5424
|
-
afterError: "TaskError"
|
|
5478
|
+
afterError: "TaskError",
|
|
5479
|
+
stopCancelled: "TaskCancel"
|
|
5425
5480
|
};
|
|
5426
5481
|
/**
|
|
5427
5482
|
* Map canonical camelCase event names to Copilot camelCase.
|
|
@@ -5739,8 +5794,11 @@ const QWENCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
5739
5794
|
const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
5740
5795
|
preToolUse: "PreToolUse",
|
|
5741
5796
|
postToolUse: "PostToolUse",
|
|
5797
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
5798
|
+
permissionRequest: "PermissionRequest",
|
|
5742
5799
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
5743
5800
|
stop: "Stop",
|
|
5801
|
+
stopFailure: "StopFailure",
|
|
5744
5802
|
sessionStart: "SessionStart",
|
|
5745
5803
|
sessionEnd: "SessionEnd",
|
|
5746
5804
|
subagentStop: "SubagentStop",
|
|
@@ -7631,11 +7689,15 @@ const KiroPermissionsOverrideSchema = z.looseObject({
|
|
|
7631
7689
|
});
|
|
7632
7690
|
/**
|
|
7633
7691
|
* Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
|
|
7634
|
-
* `.codex/config.toml`. `on-failure` is a
|
|
7635
|
-
* Codex still
|
|
7636
|
-
*
|
|
7637
|
-
*
|
|
7692
|
+
* `.codex/config.toml`. `on-failure` is a deprecated alias for `on-request`
|
|
7693
|
+
* that Codex still reads, so it stays accepted (generate warns) so existing
|
|
7694
|
+
* configs round-trip. `untrusted` was retired in Codex 0.149.0 and makes
|
|
7695
|
+
* Codex refuse to start; it stays in the enum only so an existing
|
|
7696
|
+
* `.rulesync/permissions.jsonc` still parses — generate warns and does not
|
|
7697
|
+
* write it, and import does not lift it. The granular table form
|
|
7698
|
+
* (`{ granular = { … } }`) is modeled separately in the override union.
|
|
7638
7699
|
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
7700
|
+
* @see https://github.com/openai/codex/pull/39630
|
|
7639
7701
|
*/
|
|
7640
7702
|
const CodexApprovalPolicySchema = z.enum([
|
|
7641
7703
|
"untrusted",
|
|
@@ -7705,11 +7767,12 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
|
|
|
7705
7767
|
* selected directly via `default_permissions` and skips the managed profile
|
|
7706
7768
|
* entirely — canonical filesystem/network rules are ignored in that mode.
|
|
7707
7769
|
* Defaults to `:workspace` when unspecified.
|
|
7708
|
-
* - `approval_policy` — `
|
|
7770
|
+
* - `approval_policy` — `on-request` (deprecated alias `on-failure`) |
|
|
7709
7771
|
* `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
|
|
7710
7772
|
* schema has required fields that are brittle to model as typed keys).
|
|
7711
7773
|
* Defaults to `on-request` when neither the override nor the existing
|
|
7712
|
-
* config sets it.
|
|
7774
|
+
* config sets it. `untrusted` (retired in Codex 0.149.0) is still parsed
|
|
7775
|
+
* but skipped with a warning on generate, since Codex refuses to start on it.
|
|
7713
7776
|
* - `sandbox_mode` — **deprecated.** `read-only` | `workspace-write` |
|
|
7714
7777
|
* `danger-full-access`, with the sibling `sandbox_workspace_write` table
|
|
7715
7778
|
* (`network_access`, `writable_roots`, …). Codex has superseded the classic
|
|
@@ -8357,6 +8420,7 @@ const RulesyncRuleFrontmatterSchema = z.object({
|
|
|
8357
8420
|
trigger: z.optional(z.string()),
|
|
8358
8421
|
globs: z.optional(z.array(z.string()))
|
|
8359
8422
|
})),
|
|
8423
|
+
aiassistant: z.optional(z.looseObject({ apply: z.optional(z.string()) })),
|
|
8360
8424
|
devin: z.optional(z.looseObject({
|
|
8361
8425
|
trigger: z.optional(z.string()),
|
|
8362
8426
|
globs: z.optional(z.array(z.string())),
|
|
@@ -9581,6 +9645,8 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
9581
9645
|
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
9582
9646
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
9583
9647
|
"user-invocable": z.optional(z.boolean()),
|
|
9648
|
+
icon: z.optional(z.string()),
|
|
9649
|
+
color: z.optional(z.string()),
|
|
9584
9650
|
metadata: z.optional(z.looseObject({}))
|
|
9585
9651
|
})),
|
|
9586
9652
|
factorydroid: z.optional(z.looseObject({
|
|
@@ -9595,7 +9661,8 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
9595
9661
|
})),
|
|
9596
9662
|
grokcli: z.optional(z.looseObject({
|
|
9597
9663
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
9598
|
-
"user-invocable": z.optional(z.boolean())
|
|
9664
|
+
"user-invocable": z.optional(z.boolean()),
|
|
9665
|
+
paths: z.optional(z.union([z.string(), z.array(z.string())]))
|
|
9599
9666
|
})),
|
|
9600
9667
|
dsh: z.optional(z.looseObject({
|
|
9601
9668
|
whenToUse: z.optional(z.string()),
|
|
@@ -9822,8 +9889,32 @@ var RulesyncSubagent = class RulesyncSubagent extends RulesyncFile {
|
|
|
9822
9889
|
});
|
|
9823
9890
|
}
|
|
9824
9891
|
};
|
|
9825
|
-
|
|
9826
|
-
|
|
9892
|
+
const SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
|
9893
|
+
const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9894
|
+
/**
|
|
9895
|
+
* Check a skill `name` against the length and character rules, phrased for the
|
|
9896
|
+
* `authority` that enforces them (e.g. "the Agent Skills spec" or "Zed"). When
|
|
9897
|
+
* that authority does something with a bad name beyond flagging it — Zed
|
|
9898
|
+
* refuses to load the skill — `consequence` names it so the warning says what
|
|
9899
|
+
* the author is about to lose. An empty name is the caller's concern: what it
|
|
9900
|
+
* means differs per consumer.
|
|
9901
|
+
*/
|
|
9902
|
+
function collectSkillNameViolations({ name, authority, consequence }) {
|
|
9903
|
+
const violations = [];
|
|
9904
|
+
if (name.length > 64) violations.push(`\`name\` is ${name.length} characters; ${authority} allows at most 64${consequence === void 0 ? "" : ` and ${consequence}`}`);
|
|
9905
|
+
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}`}`);
|
|
9906
|
+
return violations;
|
|
9907
|
+
}
|
|
9908
|
+
/**
|
|
9909
|
+
* Report each violation as a warning prefixed with the skill file it concerns.
|
|
9910
|
+
* The path is shown POSIX-style and stripped of control characters so a hostile
|
|
9911
|
+
* directory name cannot forge log lines; `warnWithFallback` keeps the report
|
|
9912
|
+
* visible when the caller has no logger to hand.
|
|
9913
|
+
*/
|
|
9914
|
+
function warnSkillViolations({ skillPath, violations, logger }) {
|
|
9915
|
+
const shownPath = stripControlCharacters(toPosixPath(skillPath));
|
|
9916
|
+
for (const violation of violations) warnWithFallback(logger, `${shownPath}: ${violation}`);
|
|
9917
|
+
}
|
|
9827
9918
|
/**
|
|
9828
9919
|
* Whether a directory on disk can be addressed by its name.
|
|
9829
9920
|
*
|
|
@@ -10647,10 +10738,13 @@ const AmpCheckFrontmatterSchema = z.looseObject({
|
|
|
10647
10738
|
/**
|
|
10648
10739
|
* Represents an Amp code review check.
|
|
10649
10740
|
*
|
|
10650
|
-
* Amp
|
|
10651
|
-
* scoped to the project (`.agents/checks/`) and user-wide (`~/.config/amp/checks
|
|
10652
|
-
*
|
|
10653
|
-
*
|
|
10741
|
+
* Amp reads code review checks as Markdown files with YAML frontmatter,
|
|
10742
|
+
* scoped to the project (`.agents/checks/`) and user-wide (`~/.config/amp/checks/`;
|
|
10743
|
+
* Amp also reads `~/.config/agents/checks/`, which rulesync does not write).
|
|
10744
|
+
* Since the docs rebuild the supported runner is the official
|
|
10745
|
+
* `@amp/review-checks` plugin (`amp review` is deprecated); existing checks in
|
|
10746
|
+
* this format continue to apply.
|
|
10747
|
+
* @see https://ampcode.com/docs/review
|
|
10654
10748
|
*/
|
|
10655
10749
|
var AmpCheck = class AmpCheck extends ToolCheck {
|
|
10656
10750
|
frontmatter;
|
|
@@ -16890,10 +16984,8 @@ const AgentsSkillsSkillFrontmatterSchema = z.looseObject({
|
|
|
16890
16984
|
metadata: z.optional(z.looseObject({})),
|
|
16891
16985
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
|
|
16892
16986
|
});
|
|
16893
|
-
const NAME_MAX_LENGTH = 64;
|
|
16894
|
-
const DESCRIPTION_MAX_LENGTH = 1024;
|
|
16895
16987
|
const COMPATIBILITY_MAX_LENGTH = 500;
|
|
16896
|
-
const
|
|
16988
|
+
const AGENT_SKILLS_SPEC = "the Agent Skills spec";
|
|
16897
16989
|
/**
|
|
16898
16990
|
* Placeholder for an object that has already been encoded once in the same
|
|
16899
16991
|
* value. YAML anchors let one document reference the same node repeatedly, and
|
|
@@ -17057,12 +17149,14 @@ function collectAgentSkillViolations({ frontmatter, dirName, sourceAllowedTools
|
|
|
17057
17149
|
const { name, description } = frontmatter;
|
|
17058
17150
|
if (name.length === 0) violations.push("`name` is required and must not be empty");
|
|
17059
17151
|
else {
|
|
17060
|
-
|
|
17061
|
-
|
|
17152
|
+
violations.push(...collectSkillNameViolations({
|
|
17153
|
+
name,
|
|
17154
|
+
authority: AGENT_SKILLS_SPEC
|
|
17155
|
+
}));
|
|
17062
17156
|
if (name !== dirName) violations.push(`\`name\` "${name}" must match its parent directory name "${dirName}"; conformant clients require them to be equal`);
|
|
17063
17157
|
}
|
|
17064
17158
|
if (description.length === 0) violations.push(EMPTY_SKILL_DESCRIPTION_VIOLATION);
|
|
17065
|
-
else if (description.length >
|
|
17159
|
+
else if (description.length > 1024) violations.push(`\`description\` is ${description.length} characters; ${AGENT_SKILLS_SPEC} allows at most ${SKILL_DESCRIPTION_MAX_LENGTH}`);
|
|
17066
17160
|
const { compatibility } = frontmatter;
|
|
17067
17161
|
if (typeof compatibility !== "string" && compatibility !== void 0) violations.push("`compatibility` must be a string; the Agent Skills spec does not allow a mapping here");
|
|
17068
17162
|
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}`);
|
|
@@ -17180,16 +17274,19 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
|
|
|
17180
17274
|
* directory rather than a same-named project path.
|
|
17181
17275
|
*/
|
|
17182
17276
|
static reportSpecViolations({ outputRoot, relativeDirPath, dirName, frontmatter, sourceAllowedTools, logger }) {
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17277
|
+
warnSkillViolations({
|
|
17278
|
+
skillPath: agentSkillFilePath({
|
|
17279
|
+
outputRoot,
|
|
17280
|
+
relativeDirPath,
|
|
17281
|
+
dirName
|
|
17282
|
+
}),
|
|
17283
|
+
violations: collectAgentSkillViolations({
|
|
17284
|
+
frontmatter,
|
|
17285
|
+
dirName,
|
|
17286
|
+
sourceAllowedTools
|
|
17287
|
+
}),
|
|
17288
|
+
logger
|
|
17187
17289
|
});
|
|
17188
|
-
for (const violation of collectAgentSkillViolations({
|
|
17189
|
-
frontmatter,
|
|
17190
|
-
dirName,
|
|
17191
|
-
sourceAllowedTools
|
|
17192
|
-
})) warnWithFallback(logger, `${stripControlCharacters(toPosixPath(skillPath))}: ${violation}`);
|
|
17193
17290
|
}
|
|
17194
17291
|
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
17195
17292
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
@@ -17829,6 +17926,15 @@ const KiloCommandFrontmatterSchema = z.looseObject({
|
|
|
17829
17926
|
model: z.optional(z.string()),
|
|
17830
17927
|
variant: z.optional(z.string())
|
|
17831
17928
|
});
|
|
17929
|
+
/**
|
|
17930
|
+
* Command names Kilo keeps for itself. Its loader throws on a custom command
|
|
17931
|
+
* file named `goal.md` in either the project or the global directory ("The
|
|
17932
|
+
* /goal command is reserved for session goals. Rename the custom command."),
|
|
17933
|
+
* and a throw there aborts the whole command scan — every other custom
|
|
17934
|
+
* command becomes unreachable too, not only the reserved one.
|
|
17935
|
+
* https://github.com/Kilo-Org/kilocode/blob/v7.6.2/packages/opencode/src/command/index.ts
|
|
17936
|
+
*/
|
|
17937
|
+
const KILO_RESERVED_COMMAND_NAMES = /* @__PURE__ */ new Set(["goal"]);
|
|
17832
17938
|
var KiloCommand = class KiloCommand extends ToolCommand {
|
|
17833
17939
|
frontmatter;
|
|
17834
17940
|
body;
|
|
@@ -17871,6 +17977,20 @@ var KiloCommand = class KiloCommand extends ToolCommand {
|
|
|
17871
17977
|
validate: true
|
|
17872
17978
|
});
|
|
17873
17979
|
}
|
|
17980
|
+
/**
|
|
17981
|
+
* Refuse to write a command Kilo reserves (see
|
|
17982
|
+
* {@link KILO_RESERVED_COMMAND_NAMES}). The name Kilo loads is the file
|
|
17983
|
+
* stem, and the processor calls this on the flattened path, so `goal.md`
|
|
17984
|
+
* is refused at any nesting depth under the basename naming and only at the
|
|
17985
|
+
* top level under the path naming, where `git/goal.md` becomes `git-goal`.
|
|
17986
|
+
* Import is untouched: a `goal.md` already in `.kilo/commands/` is read as
|
|
17987
|
+
* it is, since the reservation is Kilo's to enforce on its own files.
|
|
17988
|
+
*/
|
|
17989
|
+
static getWriteBlockReason({ rulesyncCommand }) {
|
|
17990
|
+
const stem = basename(rulesyncCommand.getRelativeFilePath(), ".md");
|
|
17991
|
+
if (!KILO_RESERVED_COMMAND_NAMES.has(stem)) return null;
|
|
17992
|
+
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\`.`;
|
|
17993
|
+
}
|
|
17874
17994
|
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
17875
17995
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
17876
17996
|
const kiloFields = rulesyncFrontmatter.kilo ?? {};
|
|
@@ -20093,6 +20213,14 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
20093
20213
|
if (!factory.class.isTargetedByRulesyncCommand(rulesyncCommand)) return null;
|
|
20094
20214
|
const originalRelativePath = rulesyncCommand.getRelativeFilePath();
|
|
20095
20215
|
const commandToConvert = factory.meta.supportsSubdirectory ? rulesyncCommand : this.flattenRelativeFilePath(rulesyncCommand);
|
|
20216
|
+
const writeBlockReason = factory.class.getWriteBlockReason?.({
|
|
20217
|
+
rulesyncCommand: commandToConvert,
|
|
20218
|
+
global: this.global
|
|
20219
|
+
});
|
|
20220
|
+
if (writeBlockReason !== void 0 && writeBlockReason !== null) {
|
|
20221
|
+
this.logger.warn(`Skipping command ${quoteForLog(originalRelativePath)} for '${this.toolTarget}': ${writeBlockReason}`);
|
|
20222
|
+
return null;
|
|
20223
|
+
}
|
|
20096
20224
|
if (!factory.meta.supportsSubdirectory) {
|
|
20097
20225
|
const flattenedPath = commandToConvert.getRelativeFilePath();
|
|
20098
20226
|
const firstOrigin = flattenedPathOrigins.get(flattenedPath);
|
|
@@ -20962,6 +21090,72 @@ function isSupportedHookType({ type, converterConfig }) {
|
|
|
20962
21090
|
return converterConfig.supportedHookTypes?.has(type ?? "command") ?? true;
|
|
20963
21091
|
}
|
|
20964
21092
|
/**
|
|
21093
|
+
* Whether a keyword field applies to this hook, warning about the reason when
|
|
21094
|
+
* it does not. Shared by both directions: the field is unusable on the tool
|
|
21095
|
+
* side either way, so the value is dropped rather than carried into a config
|
|
21096
|
+
* the tool would ignore or refuse.
|
|
21097
|
+
*/
|
|
21098
|
+
function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }) {
|
|
21099
|
+
const { canonical, tool, commandOnly, events } = spec;
|
|
21100
|
+
const field = direction === "generated" ? canonical : tool;
|
|
21101
|
+
if (!isFieldApplicable({
|
|
21102
|
+
commandOnly,
|
|
21103
|
+
hookType
|
|
21104
|
+
})) {
|
|
21105
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not ${direction}.`);
|
|
21106
|
+
return false;
|
|
21107
|
+
}
|
|
21108
|
+
if (events !== void 0 && !events.has(eventName)) {
|
|
21109
|
+
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}.`);
|
|
21110
|
+
return false;
|
|
21111
|
+
}
|
|
21112
|
+
return true;
|
|
21113
|
+
}
|
|
21114
|
+
/**
|
|
21115
|
+
* Emit the keyword passthrough fields: a canonical `true` becomes the tool's
|
|
21116
|
+
* keyword, while `false` is omitted as the tool's own default. A `true` the
|
|
21117
|
+
* tool would not honour on this hook is warned about, like the other kinds;
|
|
21118
|
+
* a `false` there is dropped silently, since omitting it changes nothing.
|
|
21119
|
+
*/
|
|
21120
|
+
function emitKeywordFields({ def, hookType, eventName, fields, warn }) {
|
|
21121
|
+
const emitted = {};
|
|
21122
|
+
for (const spec of fields) {
|
|
21123
|
+
if (def[spec.canonical] !== true) continue;
|
|
21124
|
+
if (isKeywordFieldApplicable({
|
|
21125
|
+
spec,
|
|
21126
|
+
hookType,
|
|
21127
|
+
eventName,
|
|
21128
|
+
direction: "generated",
|
|
21129
|
+
warn
|
|
21130
|
+
})) emitted[spec.tool] = spec.trueValue;
|
|
21131
|
+
}
|
|
21132
|
+
return emitted;
|
|
21133
|
+
}
|
|
21134
|
+
/**
|
|
21135
|
+
* Import the keyword passthrough fields back into canonical booleans,
|
|
21136
|
+
* reversing {@link emitKeywordFields}. Only the two documented keywords are
|
|
21137
|
+
* read; anything else is warned about and dropped, so a typo in a hand-written
|
|
21138
|
+
* tool file never lands in `.rulesync/hooks.*` as a boolean it did not mean.
|
|
21139
|
+
*/
|
|
21140
|
+
function importKeywordFields({ h, hookType, eventName, fields, warn }) {
|
|
21141
|
+
const imported = {};
|
|
21142
|
+
for (const spec of fields) {
|
|
21143
|
+
const value = h[spec.tool];
|
|
21144
|
+
if (value === void 0) continue;
|
|
21145
|
+
if (!isKeywordFieldApplicable({
|
|
21146
|
+
spec,
|
|
21147
|
+
hookType,
|
|
21148
|
+
eventName,
|
|
21149
|
+
direction: "imported",
|
|
21150
|
+
warn
|
|
21151
|
+
})) continue;
|
|
21152
|
+
if (value === spec.trueValue) imported[spec.canonical] = true;
|
|
21153
|
+
else if (spec.falseValue !== void 0 && value === spec.falseValue) imported[spec.canonical] = false;
|
|
21154
|
+
else warn?.(`Dropping "${spec.tool}" from an imported "${hookType}" hook on "${eventName}": ${quoteValueForWarning(value)} is not a value this tool documents for it.`);
|
|
21155
|
+
}
|
|
21156
|
+
return imported;
|
|
21157
|
+
}
|
|
21158
|
+
/**
|
|
20965
21159
|
* Emit every per-hook passthrough kind for one canonical definition.
|
|
20966
21160
|
*/
|
|
20967
21161
|
function emitAllPassthroughFields({ def, hookType, eventName, converterConfig, warn }) {
|
|
@@ -20974,6 +21168,13 @@ function emitAllPassthroughFields({ def, hookType, eventName, converterConfig, w
|
|
|
20974
21168
|
isValid: isBooleanValue,
|
|
20975
21169
|
warn
|
|
20976
21170
|
}),
|
|
21171
|
+
...emitKeywordFields({
|
|
21172
|
+
def,
|
|
21173
|
+
hookType,
|
|
21174
|
+
eventName,
|
|
21175
|
+
fields: converterConfig.keywordPassthroughFields ?? [],
|
|
21176
|
+
warn
|
|
21177
|
+
}),
|
|
20977
21178
|
...emitPassthroughFields({
|
|
20978
21179
|
def,
|
|
20979
21180
|
hookType,
|
|
@@ -21302,7 +21503,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
21302
21503
|
* Import every per-hook passthrough kind for one tool hook record, reversing
|
|
21303
21504
|
* the emit side in {@link buildToolHooks}.
|
|
21304
21505
|
*/
|
|
21305
|
-
function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
21506
|
+
function importAllPassthroughFields({ h, hookType, eventName, converterConfig, warn }) {
|
|
21306
21507
|
return {
|
|
21307
21508
|
...importPassthroughFields({
|
|
21308
21509
|
h,
|
|
@@ -21311,6 +21512,13 @@ function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
|
21311
21512
|
isValid: isBooleanValue,
|
|
21312
21513
|
warn
|
|
21313
21514
|
}),
|
|
21515
|
+
...importKeywordFields({
|
|
21516
|
+
h,
|
|
21517
|
+
hookType,
|
|
21518
|
+
eventName,
|
|
21519
|
+
fields: converterConfig.keywordPassthroughFields ?? [],
|
|
21520
|
+
warn
|
|
21521
|
+
}),
|
|
21314
21522
|
...importPassthroughFields({
|
|
21315
21523
|
h,
|
|
21316
21524
|
hookType,
|
|
@@ -21356,7 +21564,7 @@ function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
|
21356
21564
|
/**
|
|
21357
21565
|
* Convert a single tool hook record into a canonical hook definition.
|
|
21358
21566
|
*/
|
|
21359
|
-
function toolHookToCanonical({ h, rawEntry, converterConfig, warn }) {
|
|
21567
|
+
function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn }) {
|
|
21360
21568
|
const hookType = resolveImportedHookType({
|
|
21361
21569
|
value: h.type,
|
|
21362
21570
|
converterConfig
|
|
@@ -21404,6 +21612,7 @@ function toolHookToCanonical({ h, rawEntry, converterConfig, warn }) {
|
|
|
21404
21612
|
...importAllPassthroughFields({
|
|
21405
21613
|
h,
|
|
21406
21614
|
hookType,
|
|
21615
|
+
eventName,
|
|
21407
21616
|
converterConfig,
|
|
21408
21617
|
warn
|
|
21409
21618
|
}),
|
|
@@ -21490,7 +21699,7 @@ function describeHookSkipReason({ h, rawEntry, hookType, converterConfig }) {
|
|
|
21490
21699
|
/**
|
|
21491
21700
|
* Convert a single tool matcher entry into canonical hook definitions.
|
|
21492
21701
|
*/
|
|
21493
|
-
function toolMatcherEntryToCanonical({ rawEntry, converterConfig, warn }) {
|
|
21702
|
+
function toolMatcherEntryToCanonical({ rawEntry, eventName, converterConfig, warn }) {
|
|
21494
21703
|
const hookDefs = rawEntry.hooks ?? [];
|
|
21495
21704
|
const groupSkipReason = describeGroupSkipReason({
|
|
21496
21705
|
rawEntry,
|
|
@@ -21518,6 +21727,7 @@ function toolMatcherEntryToCanonical({ rawEntry, converterConfig, warn }) {
|
|
|
21518
21727
|
definitions.push(toolHookToCanonical({
|
|
21519
21728
|
h,
|
|
21520
21729
|
rawEntry,
|
|
21730
|
+
eventName,
|
|
21521
21731
|
converterConfig,
|
|
21522
21732
|
warn
|
|
21523
21733
|
}));
|
|
@@ -21565,6 +21775,7 @@ function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
|
21565
21775
|
if (!isToolMatcherEntry(rawEntry)) continue;
|
|
21566
21776
|
defs.push(...toolMatcherEntryToCanonical({
|
|
21567
21777
|
rawEntry,
|
|
21778
|
+
eventName,
|
|
21568
21779
|
converterConfig,
|
|
21569
21780
|
warn
|
|
21570
21781
|
}));
|
|
@@ -23450,14 +23661,23 @@ var ContinueHooks = class extends SettingsJsonHooks {
|
|
|
23450
23661
|
* portable `command` field is written, which upstream copies to both when
|
|
23451
23662
|
* neither is present. Note the cloud agent runs hooks in a Linux sandbox and
|
|
23452
23663
|
* honors only `bash` and `command` — a `powershell` entry is ignored there.
|
|
23664
|
+
* VS Code reads the same file and maps `bash` to `osx` / `linux` and
|
|
23665
|
+
* `powershell` to `windows`; it also documents its own per-OS overrides
|
|
23666
|
+
* (`windows`, `linux`, `osx`) next to `command`, which are typed here so an
|
|
23667
|
+
* entry that uses them instead of a shell field is not mistaken for one
|
|
23668
|
+
* without a command.
|
|
23453
23669
|
*
|
|
23454
23670
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23671
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23455
23672
|
*/
|
|
23456
23673
|
const CopilotHookEntrySchema = z.looseObject({
|
|
23457
23674
|
type: z.string(),
|
|
23458
23675
|
bash: z.optional(z.string()),
|
|
23459
23676
|
powershell: z.optional(z.string()),
|
|
23460
23677
|
command: z.optional(z.string()),
|
|
23678
|
+
windows: z.optional(z.string()),
|
|
23679
|
+
linux: z.optional(z.string()),
|
|
23680
|
+
osx: z.optional(z.string()),
|
|
23461
23681
|
cwd: z.optional(z.string()),
|
|
23462
23682
|
env: z.optional(z.record(z.string(), z.string())),
|
|
23463
23683
|
timeoutSec: z.optional(z.number()),
|
|
@@ -23548,17 +23768,30 @@ function resolveImportCommand$1(entry, logger) {
|
|
|
23548
23768
|
*
|
|
23549
23769
|
* Generate re-emits any non-canonical key verbatim through `rest`, so a key
|
|
23550
23770
|
* dropped here does not survive an import → generate round trip. `cwd` is a
|
|
23551
|
-
* documented Copilot hook field and was previously lost that way
|
|
23771
|
+
* documented Copilot hook field and was previously lost that way, as were the
|
|
23772
|
+
* VS Code per-OS overrides `windows` / `linux` / `osx`: an entry authored with
|
|
23773
|
+
* only those (no `command`) imported as a hook with no command at all and was
|
|
23774
|
+
* regenerated empty.
|
|
23552
23775
|
*
|
|
23553
23776
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23554
23777
|
* @see https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks
|
|
23778
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23555
23779
|
*/
|
|
23556
23780
|
function importPassthrough$1(entry) {
|
|
23557
23781
|
const passthrough = {};
|
|
23558
23782
|
if (entry.cwd !== void 0) passthrough.cwd = entry.cwd;
|
|
23559
23783
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
23784
|
+
for (const key of OS_OVERRIDE_KEYS) if (entry[key] !== void 0) passthrough[key] = entry[key];
|
|
23560
23785
|
return passthrough;
|
|
23561
23786
|
}
|
|
23787
|
+
const OS_OVERRIDE_KEYS = [
|
|
23788
|
+
"windows",
|
|
23789
|
+
"linux",
|
|
23790
|
+
"osx"
|
|
23791
|
+
];
|
|
23792
|
+
function hasOsOverride(passthrough) {
|
|
23793
|
+
return OS_OVERRIDE_KEYS.some((key) => passthrough[key] !== void 0);
|
|
23794
|
+
}
|
|
23562
23795
|
/**
|
|
23563
23796
|
* Extract hooks from Copilot hooks JSON into canonical format.
|
|
23564
23797
|
* Copilot format: { version: 1, hooks: { eventName: [...hookEntries] } }
|
|
@@ -23579,12 +23812,14 @@ function copilotHooksToCanonical(copilotHooks, logger) {
|
|
|
23579
23812
|
const entry = parseResult.data;
|
|
23580
23813
|
const { command, shell } = resolveImportCommand$1(entry, logger);
|
|
23581
23814
|
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
23815
|
+
const passthrough = importPassthrough$1(entry);
|
|
23816
|
+
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.`);
|
|
23582
23817
|
defs.push({
|
|
23583
23818
|
type: "command",
|
|
23584
23819
|
...command !== void 0 && { command },
|
|
23585
23820
|
...shell !== void 0 && { shell },
|
|
23586
23821
|
...timeout !== void 0 && { timeout },
|
|
23587
|
-
...
|
|
23822
|
+
...passthrough
|
|
23588
23823
|
});
|
|
23589
23824
|
}
|
|
23590
23825
|
if (defs.length > 0) canonical[eventName] = defs;
|
|
@@ -23747,6 +23982,8 @@ const CopilotCliHookEntrySchema = z.looseObject({
|
|
|
23747
23982
|
bash: z.optional(z.string()),
|
|
23748
23983
|
powershell: z.optional(z.string()),
|
|
23749
23984
|
command: z.optional(z.string()),
|
|
23985
|
+
exec: z.optional(z.string()),
|
|
23986
|
+
args: z.optional(z.array(z.string())),
|
|
23750
23987
|
prompt: z.optional(z.string()),
|
|
23751
23988
|
url: z.optional(z.string()),
|
|
23752
23989
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
@@ -23776,6 +24013,32 @@ function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger
|
|
|
23776
24013
|
return {};
|
|
23777
24014
|
}
|
|
23778
24015
|
/**
|
|
24016
|
+
* Pick the command fields of an exported `command` entry.
|
|
24017
|
+
*
|
|
24018
|
+
* A canonical `args` list (an empty one counts) selects the CLI's shell-free
|
|
24019
|
+
* form: `command` becomes `exec` and the list is written as `args`, so the
|
|
24020
|
+
* executable is spawned directly. The `shell` selector is meaningless there
|
|
24021
|
+
* and is dropped with a warning. Otherwise the canonical `shell` selector
|
|
24022
|
+
* picks the shell-specific field; without it the portable `command` field is
|
|
24023
|
+
* written, which upstream copies to both `bash` and `powershell`. Keying off
|
|
24024
|
+
* `shell` rather than `process.platform` keeps the generated file identical
|
|
24025
|
+
* on every machine.
|
|
24026
|
+
*/
|
|
24027
|
+
function resolveExportCommandPart({ def, eventName, logger }) {
|
|
24028
|
+
if (Array.isArray(def.args)) {
|
|
24029
|
+
if (def.shell !== void 0) logger?.warn(`Copilot CLI runs an \`exec\` hook without a shell; dropping shell "${def.shell}" on '${eventName}'.`);
|
|
24030
|
+
return compact({
|
|
24031
|
+
exec: def.command,
|
|
24032
|
+
args: def.args,
|
|
24033
|
+
env: def.env
|
|
24034
|
+
});
|
|
24035
|
+
}
|
|
24036
|
+
return compact({
|
|
24037
|
+
[def.shell ?? "command"]: def.command,
|
|
24038
|
+
env: def.env
|
|
24039
|
+
});
|
|
24040
|
+
}
|
|
24041
|
+
/**
|
|
23779
24042
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
23780
24043
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
23781
24044
|
*/
|
|
@@ -23815,19 +24078,17 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
23815
24078
|
...timeoutPart,
|
|
23816
24079
|
...rest
|
|
23817
24080
|
});
|
|
23818
|
-
else if (hookType === "command") {
|
|
23819
|
-
|
|
23820
|
-
|
|
23821
|
-
|
|
23822
|
-
|
|
23823
|
-
|
|
23824
|
-
|
|
23825
|
-
|
|
23826
|
-
|
|
23827
|
-
|
|
23828
|
-
|
|
23829
|
-
});
|
|
23830
|
-
}
|
|
24081
|
+
else if (hookType === "command") entries.push({
|
|
24082
|
+
type: "command",
|
|
24083
|
+
...matcherPart,
|
|
24084
|
+
...resolveExportCommandPart({
|
|
24085
|
+
def,
|
|
24086
|
+
eventName,
|
|
24087
|
+
logger
|
|
24088
|
+
}),
|
|
24089
|
+
...timeoutPart,
|
|
24090
|
+
...rest
|
|
24091
|
+
});
|
|
23831
24092
|
}
|
|
23832
24093
|
return entries;
|
|
23833
24094
|
}
|
|
@@ -23867,6 +24128,10 @@ function importPassthrough(entry) {
|
|
|
23867
24128
|
/**
|
|
23868
24129
|
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
23869
24130
|
*
|
|
24131
|
+
* The CLI-only `exec` form maps to canonical `command` + `args`, the exec form
|
|
24132
|
+
* shared with AugmentCode and Claude Code, so it no longer imports as a hook
|
|
24133
|
+
* with no command at all.
|
|
24134
|
+
*
|
|
23870
24135
|
* A shell-specific field carries its `shell` through so re-export writes the
|
|
23871
24136
|
* same field back. An entry using only the portable `command` field leaves
|
|
23872
24137
|
* `shell` unset, which re-export renders as the portable field again.
|
|
@@ -23880,6 +24145,13 @@ function importPassthrough(entry) {
|
|
|
23880
24145
|
function resolveImportCommand(entry, logger) {
|
|
23881
24146
|
const hasBash = typeof entry.bash === "string";
|
|
23882
24147
|
const hasPowershell = typeof entry.powershell === "string";
|
|
24148
|
+
if (typeof entry.exec === "string") {
|
|
24149
|
+
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.");
|
|
24150
|
+
return {
|
|
24151
|
+
command: entry.exec,
|
|
24152
|
+
args: entry.args ?? []
|
|
24153
|
+
};
|
|
24154
|
+
}
|
|
23883
24155
|
if (hasBash && hasPowershell) {
|
|
23884
24156
|
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.");
|
|
23885
24157
|
return {
|
|
@@ -23929,11 +24201,12 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
23929
24201
|
...passthrough
|
|
23930
24202
|
});
|
|
23931
24203
|
else {
|
|
23932
|
-
const { command, shell } = resolveImportCommand(entry, logger);
|
|
24204
|
+
const { command, shell, args } = resolveImportCommand(entry, logger);
|
|
23933
24205
|
defs.push({
|
|
23934
24206
|
type: "command",
|
|
23935
24207
|
...command !== void 0 && { command },
|
|
23936
24208
|
...shell !== void 0 && { shell },
|
|
24209
|
+
...args !== void 0 && { args },
|
|
23937
24210
|
...matcherPart,
|
|
23938
24211
|
...timeoutPart,
|
|
23939
24212
|
...passthrough
|
|
@@ -24483,6 +24756,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
24483
24756
|
...def.loop_limit !== void 0 && { loop_limit: def.loop_limit },
|
|
24484
24757
|
...def.matcher !== void 0 && def.matcher !== null && { matcher: def.matcher },
|
|
24485
24758
|
...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
|
|
24759
|
+
...def.type === "prompt" && def.model !== void 0 && def.model !== null && { model: def.model },
|
|
24486
24760
|
...def.failClosed !== void 0 && def.failClosed !== null && { failClosed: def.failClosed }
|
|
24487
24761
|
}));
|
|
24488
24762
|
if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
|
|
@@ -25185,7 +25459,15 @@ const GOOSE_CONVERTER_CONFIG = {
|
|
|
25185
25459
|
toolToCanonicalEventNames: GOOSE_TO_CANONICAL_EVENT_NAMES,
|
|
25186
25460
|
projectDirVar: "",
|
|
25187
25461
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
25188
|
-
wildcardMatcherMeansAll: true
|
|
25462
|
+
wildcardMatcherMeansAll: true,
|
|
25463
|
+
keywordPassthroughFields: [{
|
|
25464
|
+
canonical: "failClosed",
|
|
25465
|
+
tool: "on_failure",
|
|
25466
|
+
trueValue: "block",
|
|
25467
|
+
falseValue: "allow",
|
|
25468
|
+
commandOnly: true,
|
|
25469
|
+
events: /* @__PURE__ */ new Set(["preToolUse"])
|
|
25470
|
+
}]
|
|
25189
25471
|
};
|
|
25190
25472
|
/**
|
|
25191
25473
|
* Represents a Goose lifecycle hooks file.
|
|
@@ -25494,7 +25776,7 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
25494
25776
|
for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
|
|
25495
25777
|
if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
|
|
25496
25778
|
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.`);
|
|
25497
|
-
else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.
|
|
25779
|
+
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.`);
|
|
25498
25780
|
setHermesHookEntries({
|
|
25499
25781
|
result,
|
|
25500
25782
|
event: nativeEvent,
|
|
@@ -26374,7 +26656,8 @@ const KiroIdeHookEntrySchema = z.looseObject({
|
|
|
26374
26656
|
matcher: z.optional(z.string()),
|
|
26375
26657
|
action: z.optional(KiroIdeHookActionSchema),
|
|
26376
26658
|
timeout: z.optional(z.number()),
|
|
26377
|
-
enabled: z.optional(z.boolean())
|
|
26659
|
+
enabled: z.optional(z.boolean()),
|
|
26660
|
+
confirm: z.optional(HookConfirmSchema)
|
|
26378
26661
|
});
|
|
26379
26662
|
const KiroIdeHooksFileSchema = z.looseObject({
|
|
26380
26663
|
version: z.optional(z.string()),
|
|
@@ -26413,7 +26696,8 @@ function buildKiroIdeEntriesForEvent(trigger, definitions) {
|
|
|
26413
26696
|
...def.matcher !== void 0 && def.matcher !== null && def.matcher !== "" && { matcher: def.matcher },
|
|
26414
26697
|
action,
|
|
26415
26698
|
...def.timeout !== void 0 && def.timeout !== null && def.timeout >= 0 && { timeout: def.timeout },
|
|
26416
|
-
enabled: def.enabled ?? true
|
|
26699
|
+
enabled: def.enabled ?? true,
|
|
26700
|
+
...def.confirm !== void 0 && { confirm: def.confirm }
|
|
26417
26701
|
});
|
|
26418
26702
|
}
|
|
26419
26703
|
return entries;
|
|
@@ -26455,6 +26739,14 @@ function canonicalToKiroIdeHooks(config) {
|
|
|
26455
26739
|
}
|
|
26456
26740
|
return entries;
|
|
26457
26741
|
}
|
|
26742
|
+
/**
|
|
26743
|
+
* Copies the optional per-hook fields (`enabled`, `confirm`) back onto the
|
|
26744
|
+
* canonical definition.
|
|
26745
|
+
*/
|
|
26746
|
+
function copyOptionalHookFields({ entry, def }) {
|
|
26747
|
+
if (entry.enabled === false) def.enabled = false;
|
|
26748
|
+
if (entry.confirm !== void 0 && entry.confirm !== null) def.confirm = omitPrototypePollutionKeysDeep(entry.confirm);
|
|
26749
|
+
}
|
|
26458
26750
|
function kiroIdeHooksToCanonical(entries) {
|
|
26459
26751
|
const canonical = {};
|
|
26460
26752
|
for (const entry of entries) {
|
|
@@ -26478,7 +26770,10 @@ function kiroIdeHooksToCanonical(entries) {
|
|
|
26478
26770
|
if (entry.description !== void 0 && entry.description !== null) def.description = entry.description;
|
|
26479
26771
|
if (entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "") def.matcher = entry.matcher;
|
|
26480
26772
|
if (entry.timeout !== void 0 && entry.timeout !== null) def.timeout = entry.timeout;
|
|
26481
|
-
|
|
26773
|
+
copyOptionalHookFields({
|
|
26774
|
+
entry,
|
|
26775
|
+
def
|
|
26776
|
+
});
|
|
26482
26777
|
const list = lookupOwn({
|
|
26483
26778
|
record: canonical,
|
|
26484
26779
|
key: eventName
|
|
@@ -27485,10 +27780,16 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
27485
27780
|
//#endregion
|
|
27486
27781
|
//#region src/features/hooks/reasonix-hooks.ts
|
|
27487
27782
|
/**
|
|
27488
|
-
* Only
|
|
27489
|
-
* against the tool name); it is ignored on every other event.
|
|
27783
|
+
* Only the tool-scoped events honor the `match` field (an anchored regex
|
|
27784
|
+
* against the tool name); it is ignored on every other event. Mirrors
|
|
27785
|
+
* `UsesToolMatcher` in upstream `internal/hook/inspect.go`.
|
|
27490
27786
|
*/
|
|
27491
|
-
const REASONIX_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
27787
|
+
const REASONIX_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
27788
|
+
"PreToolUse",
|
|
27789
|
+
"PostToolUse",
|
|
27790
|
+
"PostToolUseFailure",
|
|
27791
|
+
"PermissionRequest"
|
|
27792
|
+
]);
|
|
27492
27793
|
const SUPPORTED_REASONIX_EVENTS = new Set(REASONIX_HOOK_EVENTS);
|
|
27493
27794
|
/**
|
|
27494
27795
|
* Convert canonical hooks config to the Reasonix `hooks` object.
|
|
@@ -27570,9 +27871,11 @@ function reasonixHooksToCanonical(hooks) {
|
|
|
27570
27871
|
* Reasonix hooks live in a Claude-Code-style but standalone JSON file —
|
|
27571
27872
|
* `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
|
|
27572
27873
|
* (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
|
|
27573
|
-
* All
|
|
27574
|
-
* PreToolUse/PostToolUse/
|
|
27575
|
-
* SubagentStop/PostLLMCall/
|
|
27874
|
+
* All thirteen upstream events have a clean canonical equivalent and are mapped:
|
|
27875
|
+
* PreToolUse/PostToolUse/PostToolUseFailure/PermissionRequest/UserPromptSubmit/
|
|
27876
|
+
* Stop/StopFailure plus SessionStart/SessionEnd/SubagentStop/PostLLMCall/
|
|
27877
|
+
* Notification/PreCompact (see REASONIX_HOOK_EVENTS).
|
|
27878
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/hook/hook.go
|
|
27576
27879
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
27577
27880
|
*/
|
|
27578
27881
|
var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
@@ -28398,6 +28701,23 @@ const PER_HOOK_ENABLED_TARGETS = /* @__PURE__ */ new Set([
|
|
|
28398
28701
|
"kiro-ide",
|
|
28399
28702
|
"zcode"
|
|
28400
28703
|
]);
|
|
28704
|
+
/** The targets whose hooks format carries the Kiro `confirm` prompt. */
|
|
28705
|
+
const PER_HOOK_CONFIRM_TARGETS = /* @__PURE__ */ new Set(["kiro-cli", "kiro-ide"]);
|
|
28706
|
+
/**
|
|
28707
|
+
* Names the shared events that carry a hook whose canonical definition
|
|
28708
|
+
* satisfies `predicate`, excluding events the target skips altogether (those
|
|
28709
|
+
* are already reported as skipped and produce no output, so warning about them
|
|
28710
|
+
* again would contradict that message).
|
|
28711
|
+
*/
|
|
28712
|
+
function emittedEventsWithHook(params) {
|
|
28713
|
+
const { factory, sharedHooks, effectiveHooks, predicate } = params;
|
|
28714
|
+
const skippedEvents = new Set(unsupportedEventNames({
|
|
28715
|
+
factory,
|
|
28716
|
+
sharedHooks,
|
|
28717
|
+
effectiveHooks
|
|
28718
|
+
}));
|
|
28719
|
+
return Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => predicate(def))).map(([event]) => event);
|
|
28720
|
+
}
|
|
28401
28721
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
28402
28722
|
["amp", {
|
|
28403
28723
|
class: AmpHooks,
|
|
@@ -28968,15 +29288,11 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
28968
29288
|
}
|
|
28969
29289
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
28970
29290
|
}
|
|
28971
|
-
|
|
28972
|
-
|
|
28973
|
-
|
|
28974
|
-
|
|
28975
|
-
|
|
28976
|
-
}));
|
|
28977
|
-
const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
|
|
28978
|
-
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(", ")}`);
|
|
28979
|
-
}
|
|
29291
|
+
this.warnAboutDroppedPerHookFields({
|
|
29292
|
+
factory,
|
|
29293
|
+
sharedHooks,
|
|
29294
|
+
effectiveHooks
|
|
29295
|
+
});
|
|
28980
29296
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
28981
29297
|
factory,
|
|
28982
29298
|
effectiveHooks
|
|
@@ -29006,6 +29322,33 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
29006
29322
|
if (auxiliaryFiles && auxiliaryFiles.length > 0) result.push(...auxiliaryFiles);
|
|
29007
29323
|
return result;
|
|
29008
29324
|
}
|
|
29325
|
+
/**
|
|
29326
|
+
* Warns about canonical per-hook fields that this target's hooks format
|
|
29327
|
+
* cannot express, so a hook does not silently change behavior on generation.
|
|
29328
|
+
* Only canonical definitions are considered: a tool-native field inside an
|
|
29329
|
+
* override block is passed through verbatim and honored by that tool.
|
|
29330
|
+
*/
|
|
29331
|
+
warnAboutDroppedPerHookFields(params) {
|
|
29332
|
+
const { factory, sharedHooks, effectiveHooks } = params;
|
|
29333
|
+
if (!PER_HOOK_ENABLED_TARGETS.has(this.toolTarget)) {
|
|
29334
|
+
const eventsWithDisabledHooks = emittedEventsWithHook({
|
|
29335
|
+
factory,
|
|
29336
|
+
sharedHooks,
|
|
29337
|
+
effectiveHooks,
|
|
29338
|
+
predicate: (def) => def.enabled === false
|
|
29339
|
+
});
|
|
29340
|
+
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(", ")}`);
|
|
29341
|
+
}
|
|
29342
|
+
if (!PER_HOOK_CONFIRM_TARGETS.has(this.toolTarget)) {
|
|
29343
|
+
const eventsWithConfirmHooks = emittedEventsWithHook({
|
|
29344
|
+
factory,
|
|
29345
|
+
sharedHooks,
|
|
29346
|
+
effectiveHooks,
|
|
29347
|
+
predicate: (def) => def.confirm !== void 0
|
|
29348
|
+
});
|
|
29349
|
+
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(", ")}`);
|
|
29350
|
+
}
|
|
29351
|
+
}
|
|
29009
29352
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
29010
29353
|
const hooks = toolFiles.filter((f) => f instanceof ToolHooks);
|
|
29011
29354
|
const logger = withToolTargetPrefix({
|
|
@@ -34807,28 +35150,60 @@ function resolveHermesTimeout(config) {
|
|
|
34807
35150
|
if (typeof config.networkTimeout === "number") return config.networkTimeout;
|
|
34808
35151
|
}
|
|
34809
35152
|
/**
|
|
34810
|
-
*
|
|
34811
|
-
*
|
|
34812
|
-
*
|
|
34813
|
-
* `
|
|
34814
|
-
*
|
|
34815
|
-
*
|
|
34816
|
-
|
|
34817
|
-
|
|
35153
|
+
* String-valued `oauth` keys Hermes reads (`tools/mcp_oauth.py`,
|
|
35154
|
+
* `tools/mcp_oauth_device.py`, v0.21.3): the callback overrides, the
|
|
35155
|
+
* pre-registered client, the CIMD document override, the DCR registration
|
|
35156
|
+
* fields, the token-request `user_agent`, the login `flow` (`device` for
|
|
35157
|
+
* RFC 8628) and the space-separated `scope` string sent on registration and
|
|
35158
|
+
* device authorization.
|
|
35159
|
+
*/
|
|
35160
|
+
const HERMES_OAUTH_STRING_KEYS = [
|
|
35161
|
+
"redirect_uri",
|
|
35162
|
+
"redirect_host",
|
|
35163
|
+
"client_id",
|
|
35164
|
+
"client_secret",
|
|
35165
|
+
"client_name",
|
|
35166
|
+
"client_metadata_url",
|
|
35167
|
+
"token_endpoint_auth_method",
|
|
35168
|
+
"application_type",
|
|
35169
|
+
"user_agent",
|
|
35170
|
+
"flow",
|
|
35171
|
+
"scope"
|
|
35172
|
+
];
|
|
35173
|
+
/**
|
|
35174
|
+
* Copies the `oauth` mapping of a server, keeping only the keys Hermes reads.
|
|
35175
|
+
*
|
|
35176
|
+
* Hermes reads a single `scope` string, never a `scopes` list; earlier Rulesync
|
|
35177
|
+
* versions wrote `scopes` regardless, which Hermes silently ignored. A `scopes`
|
|
35178
|
+
* list is therefore folded into `scope` (space-separated, the OAuth wire form)
|
|
35179
|
+
* when no `scope` is given, in both directions, so an authored list finally
|
|
35180
|
+
* reaches the authorization server and an old `config.yaml` imports cleanly.
|
|
34818
35181
|
*/
|
|
34819
35182
|
function copyHermesOauth(source) {
|
|
34820
35183
|
if (!isRecord$1(source)) return;
|
|
34821
35184
|
const oauth = {};
|
|
34822
|
-
for (const key of [
|
|
34823
|
-
|
|
34824
|
-
|
|
34825
|
-
|
|
34826
|
-
"client_secret"
|
|
34827
|
-
]) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
34828
|
-
if (typeof source.redirect_port === "number") oauth.redirect_port = source.redirect_port;
|
|
34829
|
-
if (isStringArray$2(source.scopes)) oauth.scopes = source.scopes;
|
|
35185
|
+
for (const key of HERMES_OAUTH_STRING_KEYS) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
35186
|
+
if (typeof oauth.scope !== "string" && isStringArray$2(source.scopes)) oauth.scope = source.scopes.join(" ");
|
|
35187
|
+
for (const key of ["redirect_port", "timeout"]) if (typeof source[key] === "number") oauth[key] = source[key];
|
|
35188
|
+
if (typeof source.cimd === "boolean") oauth.cimd = source.cimd;
|
|
34830
35189
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
34831
35190
|
}
|
|
35191
|
+
const HERMES_OPAQUE_MAPPING_KEYS = [
|
|
35192
|
+
"sampling",
|
|
35193
|
+
"elicitation",
|
|
35194
|
+
"identity_header",
|
|
35195
|
+
"lifecycle"
|
|
35196
|
+
];
|
|
35197
|
+
/**
|
|
35198
|
+
* Copies the advanced Hermes-recognized per-server fields that have no canonical
|
|
35199
|
+
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
35200
|
+
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
35201
|
+
* `connect_timeout` (seconds), `supports_parallel_tool_calls`, `protocol`,
|
|
35202
|
+
* `lazy`, `keepalive_interval`, `elicitation`, `trust`, and `identity_header` —
|
|
35203
|
+
* verbatim from `source` to `target`. Field names are identical on both sides
|
|
35204
|
+
* (the canonical `McpServerSchema` is a `looseObject`), so this serves export
|
|
35205
|
+
* and import alike. See the Hermes mcp-config-reference.
|
|
35206
|
+
*/
|
|
34832
35207
|
function copyHermesAdvancedFields(source, target) {
|
|
34833
35208
|
let copied = false;
|
|
34834
35209
|
if (typeof source.auth === "string") {
|
|
@@ -34860,6 +35235,14 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
34860
35235
|
target[key] = source[key];
|
|
34861
35236
|
copied = true;
|
|
34862
35237
|
}
|
|
35238
|
+
if (typeof source.protocol === "string") {
|
|
35239
|
+
target.protocol = source.protocol;
|
|
35240
|
+
copied = true;
|
|
35241
|
+
}
|
|
35242
|
+
if (typeof source.lazy === "boolean") {
|
|
35243
|
+
target.lazy = source.lazy;
|
|
35244
|
+
copied = true;
|
|
35245
|
+
}
|
|
34863
35246
|
if (typeof source.ssl_verify === "boolean" || typeof source.ssl_verify === "string") {
|
|
34864
35247
|
target.ssl_verify = source.ssl_verify;
|
|
34865
35248
|
copied = true;
|
|
@@ -34868,25 +35251,20 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
34868
35251
|
target.skip_preflight = source.skip_preflight;
|
|
34869
35252
|
copied = true;
|
|
34870
35253
|
}
|
|
34871
|
-
if (isPlainObject$1(source.sampling)) {
|
|
34872
|
-
target.sampling = omitPrototypePollutionKeys(structuredClone(source.sampling));
|
|
34873
|
-
copied = true;
|
|
34874
|
-
}
|
|
34875
35254
|
if (typeof source.keepalive_interval === "number") {
|
|
34876
35255
|
target.keepalive_interval = source.keepalive_interval;
|
|
34877
35256
|
copied = true;
|
|
34878
35257
|
}
|
|
34879
|
-
if (isPlainObject$1(source.elicitation)) {
|
|
34880
|
-
target.elicitation = omitPrototypePollutionKeys(structuredClone(source.elicitation));
|
|
34881
|
-
copied = true;
|
|
34882
|
-
}
|
|
34883
35258
|
if (typeof source.trust === "string") {
|
|
34884
35259
|
target.trust = source.trust;
|
|
34885
35260
|
copied = true;
|
|
34886
35261
|
}
|
|
34887
|
-
|
|
34888
|
-
|
|
34889
|
-
|
|
35262
|
+
for (const key of HERMES_OPAQUE_MAPPING_KEYS) {
|
|
35263
|
+
const mapping = source[key];
|
|
35264
|
+
if (isPlainObject$1(mapping)) {
|
|
35265
|
+
target[key] = omitPrototypePollutionKeys(structuredClone(mapping));
|
|
35266
|
+
copied = true;
|
|
35267
|
+
}
|
|
34890
35268
|
}
|
|
34891
35269
|
return copied;
|
|
34892
35270
|
}
|
|
@@ -35670,6 +36048,7 @@ function toKimiCodeServer({ name, server, logger }) {
|
|
|
35670
36048
|
"headers",
|
|
35671
36049
|
"bearerTokenEnvVar",
|
|
35672
36050
|
"enabled",
|
|
36051
|
+
"deferred",
|
|
35673
36052
|
"startupTimeoutMs",
|
|
35674
36053
|
"toolTimeoutMs",
|
|
35675
36054
|
"enabledTools",
|
|
@@ -36327,7 +36706,12 @@ const OpencodeMcpRemoteServerSchema = z.looseObject({
|
|
|
36327
36706
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
36328
36707
|
enabled: z._default(z.boolean(), true)
|
|
36329
36708
|
});
|
|
36330
|
-
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = [
|
|
36709
|
+
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = [
|
|
36710
|
+
"timeout",
|
|
36711
|
+
"oauth",
|
|
36712
|
+
"protocol",
|
|
36713
|
+
"codemode"
|
|
36714
|
+
];
|
|
36331
36715
|
const OPENCODE_MCP_TRANSPORT_KEYS = [
|
|
36332
36716
|
...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
|
|
36333
36717
|
...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
|
|
@@ -36469,7 +36853,7 @@ function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
|
36469
36853
|
* - env -> environment
|
|
36470
36854
|
* - disabled -> enabled (inverted)
|
|
36471
36855
|
* - enabledTools/disabledTools -> top-level tools map (with server name prefix)
|
|
36472
|
-
* - OpenCode-supported extras (timeout, oauth) -> passed through verbatim
|
|
36856
|
+
* - OpenCode-supported extras (timeout, oauth, protocol, codemode) -> passed through verbatim
|
|
36473
36857
|
*/
|
|
36474
36858
|
function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, logger) {
|
|
36475
36859
|
const serverRecord = serverConfig;
|
|
@@ -39462,7 +39846,7 @@ var ToolPermissions = class extends ToolFile {
|
|
|
39462
39846
|
* builtin of that name). It lives in the shared Amp settings file:
|
|
39463
39847
|
* `.amp/settings.json` (project) and `~/.config/amp/settings.json` (global).
|
|
39464
39848
|
*
|
|
39465
|
-
* Reference: https://ampcode.com/
|
|
39849
|
+
* Reference: https://ampcode.com/docs/cli/settings ("amp.tools.disable").
|
|
39466
39850
|
*/
|
|
39467
39851
|
const AMP_TOOLS_DISABLE_KEY = "amp.tools.disable";
|
|
39468
39852
|
/**
|
|
@@ -39473,19 +39857,26 @@ const AMP_TOOLS_DISABLE_KEY = "amp.tools.disable";
|
|
|
39473
39857
|
* glob. Rules are evaluated **first-match-wins**. It lives in the same shared
|
|
39474
39858
|
* Amp settings file as `amp.tools.disable`.
|
|
39475
39859
|
*
|
|
39476
|
-
* `amp.permissions` is Amp's
|
|
39477
|
-
*
|
|
39478
|
-
* argument-specific `reject` rules (the simpler `amp.tools.disable` array
|
|
39479
|
-
* only disable whole tools).
|
|
39860
|
+
* `amp.permissions` is Amp's legacy/backwards-compat surface — it remains
|
|
39861
|
+
* functional in the shipped CLI and is the only place to express `allow`/`ask`
|
|
39862
|
+
* and argument-specific `reject` rules (the simpler `amp.tools.disable` array
|
|
39863
|
+
* can only disable whole tools). The web docs no longer describe it (the
|
|
39864
|
+
* permissions section only points at custom plugins), but the shipped CLI
|
|
39865
|
+
* still lists it in its built-in settings reference (`amp --help`) and
|
|
39866
|
+
* evaluates it through `amp permissions list|test`.
|
|
39480
39867
|
*
|
|
39481
|
-
* Reference: https://ampcode.com/
|
|
39868
|
+
* Reference: https://ampcode.com/docs/tools#permissions and
|
|
39869
|
+
* https://ampcode.com/docs/customize/plugins#example-plugin-permissions.
|
|
39482
39870
|
*/
|
|
39483
39871
|
const AMP_PERMISSIONS_KEY = "amp.permissions";
|
|
39484
39872
|
/**
|
|
39485
39873
|
* The `amp.guardedFiles.allowlist` array (file globs allowed without
|
|
39486
39874
|
* confirmation), `amp.dangerouslyAllowAll` boolean (disable all confirmation),
|
|
39487
39875
|
* and `amp.mcpPermissions` array — sibling settings authored through the `amp`
|
|
39488
|
-
* permissions override.
|
|
39876
|
+
* permissions override. Only `amp.mcpPermissions` is still on the web docs
|
|
39877
|
+
* (https://ampcode.com/docs/cli/settings); the other two, like
|
|
39878
|
+
* `amp.permissions`, survive only in the CLI's built-in settings reference
|
|
39879
|
+
* (see `AMP_PERMISSIONS_KEY`).
|
|
39489
39880
|
*/
|
|
39490
39881
|
const AMP_GUARDED_FILES_ALLOWLIST_KEY = "amp.guardedFiles.allowlist";
|
|
39491
39882
|
const AMP_DANGEROUSLY_ALLOW_ALL_KEY = "amp.dangerouslyAllowAll";
|
|
@@ -42814,6 +43205,8 @@ const CODEX_DANGER_FULL_ACCESS_BASELINE = ":danger-full-access";
|
|
|
42814
43205
|
const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_EXTENDABLE_BASELINE_PROFILES);
|
|
42815
43206
|
const CODEX_DEFAULT_APPROVAL_POLICY = "on-request";
|
|
42816
43207
|
const CODEX_DEFAULT_APPROVALS_REVIEWER = "auto_review";
|
|
43208
|
+
const CODEX_RETIRED_APPROVAL_POLICY = "untrusted";
|
|
43209
|
+
const CODEX_DEPRECATED_APPROVAL_POLICY = "on-failure";
|
|
42817
43210
|
const CODEX_GLOB_SCAN_MAX_DEPTH = 8;
|
|
42818
43211
|
const CODEX_MINIMAL_KEY = ":minimal";
|
|
42819
43212
|
const CODEX_GIT_WRITE_RULES = { ".git/**": "write" };
|
|
@@ -42937,6 +43330,10 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
42937
43330
|
domainsHadUnknown
|
|
42938
43331
|
});
|
|
42939
43332
|
const override = extractCodexcliOverride(table);
|
|
43333
|
+
if (override.approval_policy === CODEX_RETIRED_APPROVAL_POLICY) {
|
|
43334
|
+
warnWithFallback(void 0, `${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}").`);
|
|
43335
|
+
delete override.approval_policy;
|
|
43336
|
+
}
|
|
42940
43337
|
if (typeof profile?.extends === "string" && CODEX_EXTENDABLE_BASELINES.has(profile.extends)) override.base_permission_profile = profile.extends;
|
|
42941
43338
|
if (defaultProfile === CODEX_DANGER_FULL_ACCESS_BASELINE) override.base_permission_profile = CODEX_DANGER_FULL_ACCESS_BASELINE;
|
|
42942
43339
|
const result = Object.keys(override).length > 0 ? {
|
|
@@ -43225,6 +43622,10 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
|
|
|
43225
43622
|
continue;
|
|
43226
43623
|
}
|
|
43227
43624
|
if (value === void 0) continue;
|
|
43625
|
+
if (key === "approval_policy" && !isWritableApprovalPolicy({
|
|
43626
|
+
value,
|
|
43627
|
+
logger
|
|
43628
|
+
})) continue;
|
|
43228
43629
|
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.`);
|
|
43229
43630
|
const existingValue = existing[key];
|
|
43230
43631
|
patch[key] = isPlainObject(existingValue) && isPlainObject(value) ? {
|
|
@@ -43232,12 +43633,37 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
|
|
|
43232
43633
|
...value
|
|
43233
43634
|
} : value;
|
|
43234
43635
|
}
|
|
43636
|
+
fillCodexcliDefaults({
|
|
43637
|
+
existing,
|
|
43638
|
+
patch,
|
|
43639
|
+
logger
|
|
43640
|
+
});
|
|
43641
|
+
return patch;
|
|
43642
|
+
}
|
|
43643
|
+
function fillCodexcliDefaults({ existing, patch, logger }) {
|
|
43235
43644
|
const defaults = {
|
|
43236
43645
|
approval_policy: CODEX_DEFAULT_APPROVAL_POLICY,
|
|
43237
43646
|
approvals_reviewer: CODEX_DEFAULT_APPROVALS_REVIEWER
|
|
43238
43647
|
};
|
|
43239
|
-
for (const [key, value] of Object.entries(defaults))
|
|
43240
|
-
|
|
43648
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
43649
|
+
if (patch[key] !== void 0) continue;
|
|
43650
|
+
if (existing[key] === void 0) {
|
|
43651
|
+
patch[key] = value;
|
|
43652
|
+
continue;
|
|
43653
|
+
}
|
|
43654
|
+
if (key === "approval_policy" && existing[key] === CODEX_RETIRED_APPROVAL_POLICY) {
|
|
43655
|
+
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}").`);
|
|
43656
|
+
patch[key] = value;
|
|
43657
|
+
}
|
|
43658
|
+
}
|
|
43659
|
+
}
|
|
43660
|
+
function isWritableApprovalPolicy({ value, logger }) {
|
|
43661
|
+
if (value === CODEX_RETIRED_APPROVAL_POLICY) {
|
|
43662
|
+
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}").`);
|
|
43663
|
+
return false;
|
|
43664
|
+
}
|
|
43665
|
+
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.`);
|
|
43666
|
+
return true;
|
|
43241
43667
|
}
|
|
43242
43668
|
function extractCodexcliOverride(table) {
|
|
43243
43669
|
const override = {};
|
|
@@ -51689,6 +52115,18 @@ const TAKT_RUNTIME_TARGETS_KEY = "targets";
|
|
|
51689
52115
|
const TAKT_RUNTIME_AUTO_ROUTING_KEY = "auto_routing";
|
|
51690
52116
|
const TAKT_RUNTIME_PROFILE_KEY = "profile";
|
|
51691
52117
|
const TAKT_RUNTIME_OPTIONS_KEY = "options";
|
|
52118
|
+
const TAKT_RUNTIME_ASSIGNMENTS_KEY = "assignments";
|
|
52119
|
+
const TAKT_RUNTIME_LADDER_KEY = "ladder";
|
|
52120
|
+
const TAKT_RUNTIME_EXTENDS_KEY = "extends";
|
|
52121
|
+
const TAKT_RUNTIME_ROUTER_PROFILE_KEY = "router_profile";
|
|
52122
|
+
const TAKT_RUNTIME_POOLS_KEY = "pools";
|
|
52123
|
+
const TAKT_RUNTIME_CANDIDATES_KEY = "candidates";
|
|
52124
|
+
const TAKT_RUNTIME_FALLBACK_PROFILE_KEY = "fallback_profile";
|
|
52125
|
+
/** The one `targets` map that routes companion reviewers rather than agents. */
|
|
52126
|
+
const TAKT_RUNTIME_COMPANIONS_TARGET_KEY = "companions";
|
|
52127
|
+
const TAKT_RUNTIME_COMPANION_KEY = "companion";
|
|
52128
|
+
const TAKT_RUNTIME_ENABLED_KEY = "enabled";
|
|
52129
|
+
const TAKT_DEFAULT_COMPANION_ENABLED = false;
|
|
51692
52130
|
const TAKT_SECURITY_POLICIES = {
|
|
51693
52131
|
workflow_arpeggio: [
|
|
51694
52132
|
"custom_data_source_modules",
|
|
@@ -51942,21 +52380,156 @@ function runtimeProfiles(runtime) {
|
|
|
51942
52380
|
const profiles = provider[TAKT_RUNTIME_PROFILES_KEY];
|
|
51943
52381
|
return isPlainObject$1(profiles) ? profiles : {};
|
|
51944
52382
|
}
|
|
52383
|
+
/** A `targets` map (`personas`, `tags`, …) with at least one non-empty entry. */
|
|
52384
|
+
function hasTargetContent(targets) {
|
|
52385
|
+
return isPlainObject$1(targets) && Object.values(targets).some(hasEntries);
|
|
52386
|
+
}
|
|
52387
|
+
/** The profile names an assignment points at: `profile` plus every `ladder` rung. */
|
|
52388
|
+
function assignmentProfileNames(assignment) {
|
|
52389
|
+
if (!isPlainObject$1(assignment)) return [];
|
|
52390
|
+
const names = [];
|
|
52391
|
+
const profile = assignment[TAKT_RUNTIME_PROFILE_KEY];
|
|
52392
|
+
if (typeof profile === "string") names.push(profile);
|
|
52393
|
+
const ladder = assignment[TAKT_RUNTIME_LADDER_KEY];
|
|
52394
|
+
if (Array.isArray(ladder)) names.push(...ladder.filter((rung) => typeof rung === "string"));
|
|
52395
|
+
return names;
|
|
52396
|
+
}
|
|
52397
|
+
/** The assignments under a `targets` map, split by whether they route companions. */
|
|
52398
|
+
function targetAssignments(targets, { companions }) {
|
|
52399
|
+
if (!isPlainObject$1(targets)) return [];
|
|
52400
|
+
return Object.entries(targets).filter(([name]) => name === TAKT_RUNTIME_COMPANIONS_TARGET_KEY === companions).flatMap(([, targetMap]) => isPlainObject$1(targetMap) ? Object.values(targetMap) : []);
|
|
52401
|
+
}
|
|
52402
|
+
/**
|
|
52403
|
+
* Record the profiles a `defaults` + `targets` pair (the top-level provider
|
|
52404
|
+
* section or one named assignment set) reaches, keeping companion roots apart
|
|
52405
|
+
* from the roots agents use.
|
|
52406
|
+
*/
|
|
52407
|
+
function addAssignmentSetRoots({ set, companionRoots, agentRoots }) {
|
|
52408
|
+
for (const name of assignmentProfileNames(set[TAKT_RUNTIME_DEFAULTS_KEY])) agentRoots.add(name);
|
|
52409
|
+
const targets = set[TAKT_RUNTIME_TARGETS_KEY];
|
|
52410
|
+
for (const assignment of targetAssignments(targets, { companions: false })) for (const name of assignmentProfileNames(assignment)) agentRoots.add(name);
|
|
52411
|
+
for (const assignment of targetAssignments(targets, { companions: true })) for (const name of assignmentProfileNames(assignment)) companionRoots.add(name);
|
|
52412
|
+
}
|
|
52413
|
+
/** The profiles `auto_routing` reaches: the router plus every pool candidate/fallback. */
|
|
52414
|
+
function addAutoRoutingRoots(autoRouting, roots) {
|
|
52415
|
+
if (!isPlainObject$1(autoRouting)) return;
|
|
52416
|
+
const routerProfile = autoRouting[TAKT_RUNTIME_ROUTER_PROFILE_KEY];
|
|
52417
|
+
if (typeof routerProfile === "string") roots.add(routerProfile);
|
|
52418
|
+
const pools = autoRouting[TAKT_RUNTIME_POOLS_KEY];
|
|
52419
|
+
if (!isPlainObject$1(pools)) return;
|
|
52420
|
+
for (const pool of Object.values(pools)) {
|
|
52421
|
+
if (!isPlainObject$1(pool)) continue;
|
|
52422
|
+
const candidates = pool[TAKT_RUNTIME_CANDIDATES_KEY];
|
|
52423
|
+
for (const candidate of Array.isArray(candidates) ? candidates : []) for (const name of assignmentProfileNames(candidate)) roots.add(name);
|
|
52424
|
+
const fallback = pool[TAKT_RUNTIME_FALLBACK_PROFILE_KEY];
|
|
52425
|
+
if (typeof fallback === "string") roots.add(fallback);
|
|
52426
|
+
}
|
|
52427
|
+
}
|
|
52428
|
+
/** Every profile reachable from `roots` by following `extends`. */
|
|
52429
|
+
function profileClosure(profiles, roots) {
|
|
52430
|
+
const closure = /* @__PURE__ */ new Set();
|
|
52431
|
+
const visit = (name) => {
|
|
52432
|
+
if (closure.has(name)) return;
|
|
52433
|
+
closure.add(name);
|
|
52434
|
+
const profile = profiles[name];
|
|
52435
|
+
const parent = isPlainObject$1(profile) ? profile[TAKT_RUNTIME_EXTENDS_KEY] : void 0;
|
|
52436
|
+
if (typeof parent === "string") visit(parent);
|
|
52437
|
+
};
|
|
52438
|
+
for (const root of roots) visit(root);
|
|
52439
|
+
return closure;
|
|
52440
|
+
}
|
|
52441
|
+
/** The `targets` map minus its `companions` entry (`undefined` when not a map). */
|
|
52442
|
+
function withoutCompanionTargets(targets) {
|
|
52443
|
+
if (!isPlainObject$1(targets)) return;
|
|
52444
|
+
const { [TAKT_RUNTIME_COMPANIONS_TARGET_KEY]: _companions, ...remaining } = targets;
|
|
52445
|
+
return remaining;
|
|
52446
|
+
}
|
|
52447
|
+
/**
|
|
52448
|
+
* The named assignment sets with their companion targets removed; a set left
|
|
52449
|
+
* with neither `defaults` nor a non-empty `targets` map is dropped, and
|
|
52450
|
+
* `undefined` means none survived.
|
|
52451
|
+
*/
|
|
52452
|
+
function withoutCompanionAssignments(assignments) {
|
|
52453
|
+
if (!isPlainObject$1(assignments)) return;
|
|
52454
|
+
const remaining = {};
|
|
52455
|
+
for (const [name, set] of Object.entries(assignments)) {
|
|
52456
|
+
if (!isPlainObject$1(set)) continue;
|
|
52457
|
+
const defaults = set[TAKT_RUNTIME_DEFAULTS_KEY];
|
|
52458
|
+
const targets = withoutCompanionTargets(set[TAKT_RUNTIME_TARGETS_KEY]);
|
|
52459
|
+
const keepTargets = hasTargetContent(targets);
|
|
52460
|
+
if (defaults === void 0 && !keepTargets) continue;
|
|
52461
|
+
remaining[name] = {
|
|
52462
|
+
...defaults === void 0 ? {} : { [TAKT_RUNTIME_DEFAULTS_KEY]: defaults },
|
|
52463
|
+
...keepTargets ? { [TAKT_RUNTIME_TARGETS_KEY]: targets } : {}
|
|
52464
|
+
};
|
|
52465
|
+
}
|
|
52466
|
+
return Object.keys(remaining).length > 0 ? remaining : void 0;
|
|
52467
|
+
}
|
|
52468
|
+
/** Whether the section (or any of its named assignment sets) routes companions at all. */
|
|
52469
|
+
function hasCompanionTargets(section) {
|
|
52470
|
+
const routesCompanions = (set) => {
|
|
52471
|
+
const targets = isPlainObject$1(set) ? set[TAKT_RUNTIME_TARGETS_KEY] : void 0;
|
|
52472
|
+
return isPlainObject$1(targets) && targets[TAKT_RUNTIME_COMPANIONS_TARGET_KEY] !== void 0;
|
|
52473
|
+
};
|
|
52474
|
+
const assignments = section[TAKT_RUNTIME_ASSIGNMENTS_KEY];
|
|
52475
|
+
return routesCompanions(section) || isPlainObject$1(assignments) && Object.values(assignments).some(routesCompanions);
|
|
52476
|
+
}
|
|
52477
|
+
/**
|
|
52478
|
+
* The `provider:` section as Takt itself evaluates it, mirroring upstream's
|
|
52479
|
+
* `getEffectiveProviderSection`: unless `companion.enabled` is `true`
|
|
52480
|
+
* (companions are opt-in), the `targets.companions` maps — top-level and inside
|
|
52481
|
+
* every named assignment set — are dropped, along with each profile that only a
|
|
52482
|
+
* companion target reaches through `extends`. A profile an agent assignment
|
|
52483
|
+
* (`defaults`, the other `targets` maps, `auto_routing`) also reaches is kept.
|
|
52484
|
+
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/schema.ts
|
|
52485
|
+
*/
|
|
52486
|
+
function effectiveRuntimeProviderSection(runtime) {
|
|
52487
|
+
const section = runtime[TAKT_RUNTIME_PROVIDER_KEY];
|
|
52488
|
+
if (!isPlainObject$1(section)) return;
|
|
52489
|
+
const companion = runtime[TAKT_RUNTIME_COMPANION_KEY];
|
|
52490
|
+
if (((isPlainObject$1(companion) ? companion[TAKT_RUNTIME_ENABLED_KEY] : void 0) ?? TAKT_DEFAULT_COMPANION_ENABLED) === true || !hasCompanionTargets(section)) return section;
|
|
52491
|
+
const profiles = runtimeProfiles(runtime);
|
|
52492
|
+
const companionRoots = /* @__PURE__ */ new Set();
|
|
52493
|
+
const agentRoots = /* @__PURE__ */ new Set();
|
|
52494
|
+
addAssignmentSetRoots({
|
|
52495
|
+
set: section,
|
|
52496
|
+
companionRoots,
|
|
52497
|
+
agentRoots
|
|
52498
|
+
});
|
|
52499
|
+
const assignments = section[TAKT_RUNTIME_ASSIGNMENTS_KEY];
|
|
52500
|
+
for (const set of isPlainObject$1(assignments) ? Object.values(assignments) : []) if (isPlainObject$1(set)) addAssignmentSetRoots({
|
|
52501
|
+
set,
|
|
52502
|
+
companionRoots,
|
|
52503
|
+
agentRoots
|
|
52504
|
+
});
|
|
52505
|
+
addAutoRoutingRoots(section[TAKT_RUNTIME_AUTO_ROUTING_KEY], agentRoots);
|
|
52506
|
+
const companionClosure = profileClosure(profiles, companionRoots);
|
|
52507
|
+
const agentClosure = profileClosure(profiles, agentRoots);
|
|
52508
|
+
const effective = {
|
|
52509
|
+
...section,
|
|
52510
|
+
[TAKT_RUNTIME_PROFILES_KEY]: Object.fromEntries(Object.entries(profiles).filter(([name]) => !companionClosure.has(name) || agentClosure.has(name)))
|
|
52511
|
+
};
|
|
52512
|
+
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];
|
|
52513
|
+
else effective[key] = value;
|
|
52514
|
+
return effective;
|
|
52515
|
+
}
|
|
51945
52516
|
/**
|
|
51946
52517
|
* Whether a parsed `runtime.yaml` puts Takt into runtime mode, mirroring
|
|
51947
|
-
* upstream's mode detection: the `provider:` section
|
|
51948
|
-
*
|
|
51949
|
-
*
|
|
51950
|
-
*
|
|
51951
|
-
*
|
|
52518
|
+
* upstream's mode detection: the effective `provider:` section (see
|
|
52519
|
+
* `effectiveRuntimeProviderSection` — companion-only routing does not count
|
|
52520
|
+
* while companions are disabled) must carry an actual assignment — a non-empty
|
|
52521
|
+
* `defaults`, `profiles` or `auto_routing`, a `targets` map with at least one
|
|
52522
|
+
* non-empty nested map, or a named assignment set with a non-empty `defaults`
|
|
52523
|
+
* or such a `targets` map. The file existing is not enough, and empty nested
|
|
52524
|
+
* maps (`defaults: {}`, `targets: { personas: {} }`) must not flip the mode.
|
|
51952
52525
|
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/mode.ts
|
|
51953
52526
|
*/
|
|
51954
52527
|
function isRuntimeModeActive(runtime) {
|
|
51955
|
-
const
|
|
51956
|
-
if (
|
|
51957
|
-
|
|
51958
|
-
const
|
|
51959
|
-
return
|
|
52528
|
+
const section = effectiveRuntimeProviderSection(runtime);
|
|
52529
|
+
if (section === void 0) return false;
|
|
52530
|
+
const assignments = section[TAKT_RUNTIME_ASSIGNMENTS_KEY];
|
|
52531
|
+
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])));
|
|
52532
|
+
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;
|
|
51960
52533
|
}
|
|
51961
52534
|
/** The provider named by the runtime profile Takt would use by default. */
|
|
51962
52535
|
function resolveRuntimeProvider(runtime) {
|
|
@@ -51979,10 +52552,13 @@ function resolveRuntimeProvider(runtime) {
|
|
|
51979
52552
|
* shape the `takt` override uses. Each profile's options belong to that
|
|
51980
52553
|
* profile's own provider; when several profiles name the same provider they are
|
|
51981
52554
|
* merged in document order, so a later profile wins on a colliding option key.
|
|
52555
|
+
* Profiles Takt itself ignores (companion-only ones while companions are
|
|
52556
|
+
* disabled) are skipped, as they never reach an agent.
|
|
51982
52557
|
*/
|
|
51983
52558
|
function collectRuntimeProviderOptions(runtime) {
|
|
51984
52559
|
const collected = {};
|
|
51985
|
-
|
|
52560
|
+
const profiles = effectiveRuntimeProviderSection(runtime)?.[TAKT_RUNTIME_PROFILES_KEY];
|
|
52561
|
+
for (const profile of Object.values(isPlainObject$1(profiles) ? profiles : {})) {
|
|
51986
52562
|
if (!isPlainObject$1(profile)) continue;
|
|
51987
52563
|
const providerName = profile[TAKT_RUNTIME_PROVIDER_KEY];
|
|
51988
52564
|
const options = profile[TAKT_RUNTIME_OPTIONS_KEY];
|
|
@@ -52021,13 +52597,20 @@ async function readTaktRuntimeFile({ filePath, logger }) {
|
|
|
52021
52597
|
};
|
|
52022
52598
|
}
|
|
52023
52599
|
}
|
|
52600
|
+
/** A map-valued key of a `provider:` section, or `undefined` when it is not a map. */
|
|
52601
|
+
function runtimeSectionMap(section, key) {
|
|
52602
|
+
return isPlainObject$1(section[key]) ? section[key] : void 0;
|
|
52603
|
+
}
|
|
52024
52604
|
/**
|
|
52025
52605
|
* Collapse the project and global `runtime.yaml` into the single document Takt
|
|
52026
|
-
* itself resolves against, matching upstream's loader: `profiles`
|
|
52027
|
-
* with the project definition of a same-named
|
|
52028
|
-
* while `defaults`, `targets` and `auto_routing` are
|
|
52029
|
-
* whole whenever it states them at all (`??`, so a
|
|
52030
|
-
* the global one rather than merging with it).
|
|
52606
|
+
* itself resolves against, matching upstream's loader: `profiles` and
|
|
52607
|
+
* `assignments` are unions with the project definition of a same-named entry
|
|
52608
|
+
* replacing the global one, while `defaults`, `targets` and `auto_routing` are
|
|
52609
|
+
* taken from the project file whole whenever it states them at all (`??`, so a
|
|
52610
|
+
* project `targets: {}` masks the global one rather than merging with it). The
|
|
52611
|
+
* top-level `companion.enabled` — which decides whether companion routing
|
|
52612
|
+
* counts for mode detection — is carried as upstream carries it: unset when
|
|
52613
|
+
* neither file states it, otherwise on only when neither file turns it off.
|
|
52031
52614
|
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/loader.ts
|
|
52032
52615
|
*
|
|
52033
52616
|
* Merging before mode detection matters in both directions: a project file
|
|
@@ -52043,14 +52626,15 @@ function mergeTaktRuntimeConfigs({ project, global: globalConfig }) {
|
|
|
52043
52626
|
const projectSection = sectionOf(project);
|
|
52044
52627
|
const globalSection = sectionOf(globalConfig);
|
|
52045
52628
|
const replaced = (key) => projectSection[key] ?? globalSection[key];
|
|
52046
|
-
const profilesOf = (section) => isPlainObject$1(section[TAKT_RUNTIME_PROFILES_KEY]) ? section[TAKT_RUNTIME_PROFILES_KEY] : void 0;
|
|
52047
|
-
const globalProfiles = profilesOf(globalSection);
|
|
52048
|
-
const projectProfiles = profilesOf(projectSection);
|
|
52049
52629
|
const provider = {};
|
|
52050
|
-
|
|
52051
|
-
|
|
52052
|
-
|
|
52053
|
-
|
|
52630
|
+
for (const key of [TAKT_RUNTIME_PROFILES_KEY, TAKT_RUNTIME_ASSIGNMENTS_KEY]) {
|
|
52631
|
+
const globalMap = runtimeSectionMap(globalSection, key);
|
|
52632
|
+
const projectMap = runtimeSectionMap(projectSection, key);
|
|
52633
|
+
if (globalMap !== void 0 || projectMap !== void 0) provider[key] = {
|
|
52634
|
+
...globalMap,
|
|
52635
|
+
...projectMap
|
|
52636
|
+
};
|
|
52637
|
+
}
|
|
52054
52638
|
for (const key of [
|
|
52055
52639
|
TAKT_RUNTIME_DEFAULTS_KEY,
|
|
52056
52640
|
TAKT_RUNTIME_TARGETS_KEY,
|
|
@@ -52059,7 +52643,17 @@ function mergeTaktRuntimeConfigs({ project, global: globalConfig }) {
|
|
|
52059
52643
|
const value = replaced(key);
|
|
52060
52644
|
if (value !== void 0) provider[key] = value;
|
|
52061
52645
|
}
|
|
52062
|
-
|
|
52646
|
+
const enabledOf = (config) => {
|
|
52647
|
+
const companion = config?.[TAKT_RUNTIME_COMPANION_KEY];
|
|
52648
|
+
return isPlainObject$1(companion) ? companion[TAKT_RUNTIME_ENABLED_KEY] : void 0;
|
|
52649
|
+
};
|
|
52650
|
+
const globalEnabled = enabledOf(globalConfig);
|
|
52651
|
+
const projectEnabled = enabledOf(project);
|
|
52652
|
+
const companion = globalEnabled === void 0 && projectEnabled === void 0 ? void 0 : { [TAKT_RUNTIME_ENABLED_KEY]: globalEnabled !== false && projectEnabled !== false };
|
|
52653
|
+
return {
|
|
52654
|
+
[TAKT_RUNTIME_PROVIDER_KEY]: provider,
|
|
52655
|
+
...companion === void 0 ? {} : { [TAKT_RUNTIME_COMPANION_KEY]: companion }
|
|
52656
|
+
};
|
|
52063
52657
|
}
|
|
52064
52658
|
/**
|
|
52065
52659
|
* The runtime provider configuration in force for a generate run: the project
|
|
@@ -58647,8 +59241,11 @@ const CursorSkillFrontmatterSchema = z.looseObject({
|
|
|
58647
59241
|
name: z.string(),
|
|
58648
59242
|
description: z.string(),
|
|
58649
59243
|
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
59244
|
+
globs: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
58650
59245
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
58651
59246
|
"user-invocable": z.optional(z.boolean()),
|
|
59247
|
+
icon: z.optional(z.string()),
|
|
59248
|
+
color: z.optional(z.string()),
|
|
58652
59249
|
metadata: z.optional(z.looseObject({}))
|
|
58653
59250
|
});
|
|
58654
59251
|
/**
|
|
@@ -58700,10 +59297,13 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
58700
59297
|
}
|
|
58701
59298
|
toRulesyncSkill() {
|
|
58702
59299
|
const frontmatter = this.getFrontmatter();
|
|
59300
|
+
const paths = frontmatter.paths ?? frontmatter.globs;
|
|
58703
59301
|
const cursorSection = {
|
|
58704
|
-
...
|
|
59302
|
+
...paths !== void 0 && { paths },
|
|
58705
59303
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
58706
59304
|
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
59305
|
+
...frontmatter.icon !== void 0 && { icon: frontmatter.icon },
|
|
59306
|
+
...frontmatter.color !== void 0 && { color: frontmatter.color },
|
|
58707
59307
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
58708
59308
|
};
|
|
58709
59309
|
const rulesyncFrontmatter = {
|
|
@@ -58745,6 +59345,8 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
58745
59345
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
58746
59346
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
58747
59347
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
59348
|
+
...cursorSection?.icon !== void 0 && { icon: cursorSection.icon },
|
|
59349
|
+
...cursorSection?.color !== void 0 && { color: cursorSection.color },
|
|
58748
59350
|
...metadata !== void 0 && { metadata }
|
|
58749
59351
|
};
|
|
58750
59352
|
return new CursorSkill({
|
|
@@ -59728,7 +60330,8 @@ const GrokcliSkillFrontmatterSchema = z.looseObject({
|
|
|
59728
60330
|
name: z.string(),
|
|
59729
60331
|
description: z.string(),
|
|
59730
60332
|
"user-invocable": z.optional(z.boolean()),
|
|
59731
|
-
"disable-model-invocation": z.optional(z.boolean())
|
|
60333
|
+
"disable-model-invocation": z.optional(z.boolean()),
|
|
60334
|
+
paths: z.optional(z.union([z.string(), z.array(z.string())]))
|
|
59732
60335
|
});
|
|
59733
60336
|
/**
|
|
59734
60337
|
* Represents a Grok Build skill directory.
|
|
@@ -59787,7 +60390,8 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
|
|
|
59787
60390
|
const frontmatter = this.getFrontmatter();
|
|
59788
60391
|
const grokcliSection = {
|
|
59789
60392
|
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
59790
|
-
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
60393
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
60394
|
+
...frontmatter.paths !== void 0 && { paths: frontmatter.paths }
|
|
59791
60395
|
};
|
|
59792
60396
|
const rulesyncFrontmatter = {
|
|
59793
60397
|
name: frontmatter.name,
|
|
@@ -59822,7 +60426,8 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
|
|
|
59822
60426
|
name: rulesyncFrontmatter.name,
|
|
59823
60427
|
description: rulesyncFrontmatter.description,
|
|
59824
60428
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
59825
|
-
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
60429
|
+
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
60430
|
+
...grokcliSection?.paths !== void 0 && { paths: grokcliSection.paths }
|
|
59826
60431
|
};
|
|
59827
60432
|
return new GrokcliSkill({
|
|
59828
60433
|
outputRoot,
|
|
@@ -62473,7 +63078,10 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
62473
63078
|
}
|
|
62474
63079
|
}
|
|
62475
63080
|
static getSettablePaths({ global: _global = false } = {}) {
|
|
62476
|
-
return {
|
|
63081
|
+
return {
|
|
63082
|
+
relativeDirPath: WARP_SKILLS_DIR_PATH,
|
|
63083
|
+
importOnlySkillRoots: [AGENTSMD_SKILLS_DIR_PATH]
|
|
63084
|
+
};
|
|
62477
63085
|
}
|
|
62478
63086
|
getFrontmatter() {
|
|
62479
63087
|
return WarpSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
@@ -62537,12 +63145,14 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
62537
63145
|
return targets.includes("*") || targets.includes("warp");
|
|
62538
63146
|
}
|
|
62539
63147
|
/**
|
|
62540
|
-
* Commands are emitted into
|
|
62541
|
-
* (see `WarpCommand`), so a directory matching a current rulesync
|
|
62542
|
-
* slug is owned by the commands feature: it must not be imported as
|
|
62543
|
-
* skill nor deleted as an orphan skill.
|
|
63148
|
+
* Commands are emitted into the `.warp/skills/` tree as `<slug>/SKILL.md`
|
|
63149
|
+
* (see `WarpCommand`), so a directory there matching a current rulesync
|
|
63150
|
+
* command slug is owned by the commands feature: it must not be imported as
|
|
63151
|
+
* a skill nor deleted as an orphan skill. The import-only `.agents/skills/`
|
|
63152
|
+
* root never receives commands, so a same-named skill in it stays a skill.
|
|
62544
63153
|
*/
|
|
62545
|
-
static async isDirOwned({ dirName, inputRoots }) {
|
|
63154
|
+
static async isDirOwned({ relativeDirPath, dirName, inputRoots }) {
|
|
63155
|
+
if (relativeDirPath !== WARP_SKILLS_DIR_PATH) return true;
|
|
62546
63156
|
return !await rulesyncCommandSlugExists({
|
|
62547
63157
|
inputRoots,
|
|
62548
63158
|
dirName
|
|
@@ -62755,6 +63365,25 @@ const ZedSkillFrontmatterSchema = z.looseObject({
|
|
|
62755
63365
|
description: z.string(),
|
|
62756
63366
|
"disable-model-invocation": z.optional(z.boolean())
|
|
62757
63367
|
});
|
|
63368
|
+
const ZED = "Zed";
|
|
63369
|
+
const ZED_LOAD_FAILURE = "does not load the skill otherwise";
|
|
63370
|
+
/**
|
|
63371
|
+
* Collect the rules Zed enforces on a skill's frontmatter that the loose
|
|
63372
|
+
* schema above does not — `name` rules first, then `description`. Returned as
|
|
63373
|
+
* warnings rather than thrown: the canonical skill is shared with every other
|
|
63374
|
+
* target, so a name Zed rejects must not stop the generate run for the rest.
|
|
63375
|
+
*/
|
|
63376
|
+
function collectZedSkillViolations({ name, description }) {
|
|
63377
|
+
const violations = [];
|
|
63378
|
+
if (name.length === 0) violations.push("`name` must not be empty; Zed does not load a skill without one");
|
|
63379
|
+
else violations.push(...collectSkillNameViolations({
|
|
63380
|
+
name,
|
|
63381
|
+
authority: ZED,
|
|
63382
|
+
consequence: ZED_LOAD_FAILURE
|
|
63383
|
+
}));
|
|
63384
|
+
if (description.length > 1024) violations.push(`\`description\` is ${description.length} characters; ${ZED} loads the skill but warns past ${SKILL_DESCRIPTION_MAX_LENGTH}`);
|
|
63385
|
+
return violations;
|
|
63386
|
+
}
|
|
62758
63387
|
/**
|
|
62759
63388
|
* Represents a Zed agent skill directory.
|
|
62760
63389
|
* Skills are stored under .agents/skills/ (project) or ~/.agents/skills/ (global)
|
|
@@ -62823,9 +63452,10 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
62823
63452
|
global: this.global
|
|
62824
63453
|
});
|
|
62825
63454
|
}
|
|
62826
|
-
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
63455
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false, logger }) {
|
|
62827
63456
|
const settablePaths = ZedSkill.getSettablePaths({ global });
|
|
62828
63457
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
63458
|
+
const dirName = rulesyncSkill.getDirName();
|
|
62829
63459
|
const zedSection = rulesyncFrontmatter.zed;
|
|
62830
63460
|
const resolvedDisableModelInvocation = resolveDisableModelInvocation({
|
|
62831
63461
|
rootFrontmatter: rulesyncFrontmatter,
|
|
@@ -62837,10 +63467,17 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
62837
63467
|
...zedSection,
|
|
62838
63468
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
62839
63469
|
};
|
|
63470
|
+
ZedSkill.reportSkillViolations({
|
|
63471
|
+
outputRoot,
|
|
63472
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
63473
|
+
dirName,
|
|
63474
|
+
frontmatter: zedFrontmatter,
|
|
63475
|
+
logger
|
|
63476
|
+
});
|
|
62840
63477
|
return new ZedSkill({
|
|
62841
63478
|
outputRoot,
|
|
62842
63479
|
relativeDirPath: settablePaths.relativeDirPath,
|
|
62843
|
-
dirName
|
|
63480
|
+
dirName,
|
|
62844
63481
|
frontmatter: zedFrontmatter,
|
|
62845
63482
|
body: rulesyncSkill.getBody(),
|
|
62846
63483
|
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
@@ -62848,6 +63485,18 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
62848
63485
|
global
|
|
62849
63486
|
});
|
|
62850
63487
|
}
|
|
63488
|
+
/**
|
|
63489
|
+
* Warn about every rule Zed enforces on the skill about to be written. The
|
|
63490
|
+
* reported path includes `outputRoot` so a global-scope skill points at the
|
|
63491
|
+
* file under the home directory rather than a same-named project path.
|
|
63492
|
+
*/
|
|
63493
|
+
static reportSkillViolations({ outputRoot, relativeDirPath, dirName, frontmatter, logger }) {
|
|
63494
|
+
warnSkillViolations({
|
|
63495
|
+
skillPath: join(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME),
|
|
63496
|
+
violations: collectZedSkillViolations(frontmatter),
|
|
63497
|
+
logger
|
|
63498
|
+
});
|
|
63499
|
+
}
|
|
62851
63500
|
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
62852
63501
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
62853
63502
|
return targets.includes("*") || targets.includes("zed");
|
|
@@ -65457,11 +66106,17 @@ var CopilotSubagent = class CopilotSubagent extends ToolSubagent {
|
|
|
65457
66106
|
*
|
|
65458
66107
|
* Reference: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli
|
|
65459
66108
|
* https://docs.github.com/en/copilot/reference/custom-agents-configuration
|
|
66109
|
+
* https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference
|
|
65460
66110
|
*
|
|
65461
66111
|
* Per the configuration docs:
|
|
65462
66112
|
* - `description` is required.
|
|
65463
66113
|
* - `name`, `target`, `tools`, `model`, `disable-model-invocation`,
|
|
65464
66114
|
* `user-invocable`, `mcp-servers`, and `metadata` are optional.
|
|
66115
|
+
* - The CLI command reference adds `models` (authored models in priority
|
|
66116
|
+
* order, overriding `model` when both are set) and `modelPolicy`
|
|
66117
|
+
* (`preferred` lets a `subagents` settings override or the `/subagents`
|
|
66118
|
+
* picker replace the model; `required` locks dispatch to the authored
|
|
66119
|
+
* list). CLI v1.0.81+; the shared reference page still lists `model` only.
|
|
65465
66120
|
*
|
|
65466
66121
|
* `looseObject` is used so that future additional fields are passed through
|
|
65467
66122
|
* unchanged (consistent with the rest of rulesync's frontmatter schemas).
|
|
@@ -65472,6 +66127,8 @@ const CopilotCliSubagentFrontmatterSchema = z.looseObject({
|
|
|
65472
66127
|
target: z.optional(z.string()),
|
|
65473
66128
|
tools: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
65474
66129
|
model: z.optional(z.string()),
|
|
66130
|
+
models: z.optional(z.array(z.string())),
|
|
66131
|
+
modelPolicy: z.optional(z.enum(["preferred", "required"])),
|
|
65475
66132
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
65476
66133
|
"user-invocable": z.optional(z.boolean()),
|
|
65477
66134
|
"mcp-servers": z.optional(z.record(z.string(), z.unknown())),
|
|
@@ -69848,16 +70505,131 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
69848
70505
|
}
|
|
69849
70506
|
};
|
|
69850
70507
|
//#endregion
|
|
70508
|
+
//#region src/utils/brace-aware-list.ts
|
|
70509
|
+
/**
|
|
70510
|
+
* Splits a comma-separated pattern list on the commas that separate its
|
|
70511
|
+
* entries, leaving the commas inside a brace group alone: in
|
|
70512
|
+
* `{src,lib}/**\/*.ts, tests/**\/*.test.ts` the first comma belongs to the
|
|
70513
|
+
* brace expansion and the second separates the two globs. Entries are trimmed;
|
|
70514
|
+
* empty entries are dropped.
|
|
70515
|
+
*
|
|
70516
|
+
* @example
|
|
70517
|
+
* splitBraceAwareList("src/**\/*.{ts,tsx}, docs/**")
|
|
70518
|
+
* // => ["src/**\/*.{ts,tsx}", "docs/**"]
|
|
70519
|
+
*/
|
|
70520
|
+
const splitBraceAwareList = (value) => {
|
|
70521
|
+
const entries = [];
|
|
70522
|
+
let current = "";
|
|
70523
|
+
let braceDepth = 0;
|
|
70524
|
+
for (const char of value) {
|
|
70525
|
+
if (char === "{") braceDepth += 1;
|
|
70526
|
+
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
70527
|
+
else if (char === "," && braceDepth === 0) {
|
|
70528
|
+
entries.push(current);
|
|
70529
|
+
current = "";
|
|
70530
|
+
continue;
|
|
70531
|
+
}
|
|
70532
|
+
current += char;
|
|
70533
|
+
}
|
|
70534
|
+
entries.push(current);
|
|
70535
|
+
return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
70536
|
+
};
|
|
70537
|
+
/**
|
|
70538
|
+
* Upper bound on the patterns one glob may expand to; past it the glob is
|
|
70539
|
+
* returned verbatim, since `{a,b}` repeated k times yields 2^k branches.
|
|
70540
|
+
*/
|
|
70541
|
+
const MAX_BRACE_EXPANSIONS = 256;
|
|
70542
|
+
/** Matches an innermost brace group with at least one comma: `{a,b}`, not `{a}` or `{a,{b,c}}`. */
|
|
70543
|
+
const INNERMOST_BRACE_GROUP_REGEX = /\{([^{},]*(?:,[^{},]*)+)\}/;
|
|
70544
|
+
/**
|
|
70545
|
+
* Expands the comma alternations of a glob (`a.{ts,tsx}` → `a.ts`, `a.tsx`)
|
|
70546
|
+
* into one pattern per combination, innermost group first, so the result can
|
|
70547
|
+
* be handed to a consumer that splits its pattern list on bare commas. A glob
|
|
70548
|
+
* without a brace alternation is returned as is; a brace group without a
|
|
70549
|
+
* comma (e.g. `{a}`) is left untouched. A glob whose expansion would exceed
|
|
70550
|
+
* `MAX_BRACE_EXPANSIONS` patterns is returned as is as well.
|
|
70551
|
+
*
|
|
70552
|
+
* The cap is enforced on the work list itself rather than by counting the
|
|
70553
|
+
* groups up front: a nested group such as `{{a,b},c}` is rewritten into one
|
|
70554
|
+
* new group per inner alternative, so the number of patterns in flight is the
|
|
70555
|
+
* only bound that holds for arbitrary nesting.
|
|
70556
|
+
*
|
|
70557
|
+
* @example
|
|
70558
|
+
* expandBraceAlternations("src/**\/*.{ts,tsx}")
|
|
70559
|
+
* // => ["src/**\/*.ts", "src/**\/*.tsx"]
|
|
70560
|
+
*/
|
|
70561
|
+
const expandBraceAlternations = (glob) => {
|
|
70562
|
+
let pending = [glob];
|
|
70563
|
+
for (;;) {
|
|
70564
|
+
const next = [];
|
|
70565
|
+
let expanded = false;
|
|
70566
|
+
for (const pattern of pending) {
|
|
70567
|
+
const match = INNERMOST_BRACE_GROUP_REGEX.exec(pattern);
|
|
70568
|
+
if (match === null) {
|
|
70569
|
+
next.push(pattern);
|
|
70570
|
+
continue;
|
|
70571
|
+
}
|
|
70572
|
+
expanded = true;
|
|
70573
|
+
const prefix = pattern.slice(0, match.index);
|
|
70574
|
+
const suffix = pattern.slice(match.index + match[0].length);
|
|
70575
|
+
for (const alternative of (match[1] ?? "").split(",")) next.push(`${prefix}${alternative}${suffix}`);
|
|
70576
|
+
if (next.length > MAX_BRACE_EXPANSIONS) return [glob];
|
|
70577
|
+
}
|
|
70578
|
+
if (!expanded) return [...new Set(next)];
|
|
70579
|
+
pending = next;
|
|
70580
|
+
}
|
|
70581
|
+
};
|
|
70582
|
+
//#endregion
|
|
69851
70583
|
//#region src/features/rules/aiassistant-rule.ts
|
|
69852
70584
|
/**
|
|
70585
|
+
* Rule types AI Assistant recognizes on the `apply:` metadata line. The values
|
|
70586
|
+
* are matched case-sensitively by the plugin, so they are emitted verbatim.
|
|
70587
|
+
* Any other value is carried through untouched (the plugin treats it as Off
|
|
70588
|
+
* today; a future build may recognize it).
|
|
70589
|
+
*/
|
|
70590
|
+
const AIASSISTANT_APPLY_VALUES = [
|
|
70591
|
+
"always",
|
|
70592
|
+
"manually",
|
|
70593
|
+
"by model decision",
|
|
70594
|
+
"by file patterns",
|
|
70595
|
+
"off"
|
|
70596
|
+
];
|
|
70597
|
+
const isAiassistantApply = (value) => AIASSISTANT_APPLY_VALUES.includes(value);
|
|
70598
|
+
/**
|
|
70599
|
+
* Globs that match the whole project and so say nothing beyond "always".
|
|
70600
|
+
*/
|
|
70601
|
+
const UNIVERSAL_GLOBS$2 = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
70602
|
+
const METADATA_DELIMITER = "---";
|
|
70603
|
+
/**
|
|
70604
|
+
* Splits text into lines that keep their own line terminator, so the body can
|
|
70605
|
+
* be sliced back out of the file byte-for-byte. Lone `\r` counts as a break,
|
|
70606
|
+
* matching Kotlin's `lines()`, which the plugin uses on the block.
|
|
70607
|
+
*/
|
|
70608
|
+
const LINE_SPLIT_REGEX = /(?<=\r\n|\r(?!\n)|(?<!\r)\n)/;
|
|
70609
|
+
/**
|
|
70610
|
+
* The block is line-based, so a value must not carry a line break: one would
|
|
70611
|
+
* start a new `<field>: <value>` line and could override `apply:`.
|
|
70612
|
+
*/
|
|
70613
|
+
const flattenValue = (value) => value.split(/\r\n|\r|\n/).map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
|
|
70614
|
+
/**
|
|
69853
70615
|
* Rule generator for JetBrains AI Assistant.
|
|
69854
70616
|
*
|
|
69855
70617
|
* AI Assistant reads project rules as flat Markdown files in
|
|
69856
|
-
* `.aiassistant/rules/*.md` (one file per rule, identified by filename).
|
|
69857
|
-
*
|
|
69858
|
-
*
|
|
69859
|
-
*
|
|
69860
|
-
*
|
|
70618
|
+
* `.aiassistant/rules/*.md` (one file per rule, identified by filename). There
|
|
70619
|
+
* is no special root file (unlike Junie's `guidelines.md`), so every rule —
|
|
70620
|
+
* root or non-root — is written as its own file under `rules/`.
|
|
70621
|
+
*
|
|
70622
|
+
* The rule type (Always / Manually / By model decision / By file patterns /
|
|
70623
|
+
* Off) is stored in the file itself as a leading `---` metadata block that the
|
|
70624
|
+
* IDE's "Rule type" selector edits in place. The block is not YAML: each line
|
|
70625
|
+
* is `<field>: <value>` split on the first colon, and `patterns` is a
|
|
70626
|
+
* comma-separated glob list, so it is written and parsed by hand here. A file
|
|
70627
|
+
* without an `apply:` line is treated as Off at retrieval time (it is never
|
|
70628
|
+
* attached automatically), which is why every generated file carries one.
|
|
70629
|
+
*
|
|
70630
|
+
* Field names and values come from the plugin (`AiRulesService`,
|
|
70631
|
+
* `AiRulesLLMBundle.properties`; verified on build 262.10968.75) — the docs
|
|
70632
|
+
* page describes the rule type only as an IDE setting.
|
|
69861
70633
|
*
|
|
69862
70634
|
* AI Assistant and Junie are different JetBrains products (real-time assistance
|
|
69863
70635
|
* vs. autonomous agent) with different layouts, so this is a separate target.
|
|
@@ -69865,18 +70637,154 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
69865
70637
|
* @see https://www.jetbrains.com/help/ai-assistant/configure-project-rules.html
|
|
69866
70638
|
*/
|
|
69867
70639
|
var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
70640
|
+
body;
|
|
70641
|
+
metadata;
|
|
70642
|
+
constructor({ body, metadata, ...rest }) {
|
|
70643
|
+
super({
|
|
70644
|
+
...rest,
|
|
70645
|
+
fileContent: AiassistantRule.buildFileContent({
|
|
70646
|
+
body,
|
|
70647
|
+
metadata
|
|
70648
|
+
})
|
|
70649
|
+
});
|
|
70650
|
+
this.body = body;
|
|
70651
|
+
this.metadata = metadata;
|
|
70652
|
+
}
|
|
69868
70653
|
static getSettablePaths(_options = {}) {
|
|
69869
70654
|
return { nonRoot: { relativeDirPath: AIASSISTANT_RULES_DIR_PATH } };
|
|
69870
70655
|
}
|
|
70656
|
+
getBody() {
|
|
70657
|
+
return this.body;
|
|
70658
|
+
}
|
|
70659
|
+
getMetadata() {
|
|
70660
|
+
return this.metadata;
|
|
70661
|
+
}
|
|
70662
|
+
/**
|
|
70663
|
+
* Serialize the metadata block the way the IDE writes it
|
|
70664
|
+
* (`---\n<field>: <value>\n---`), followed by the body.
|
|
70665
|
+
*/
|
|
70666
|
+
static buildFileContent({ body, metadata }) {
|
|
70667
|
+
if (metadata === void 0) return body;
|
|
70668
|
+
const lines = [`apply: ${metadata.apply}`];
|
|
70669
|
+
if (metadata.apply === "by model decision" && metadata.instructions) lines.push(`instructions: ${flattenValue(metadata.instructions)}`);
|
|
70670
|
+
if (metadata.apply === "by file patterns" && metadata.patterns && metadata.patterns.length > 0) lines.push(`patterns: ${metadata.patterns.map(flattenValue).join(", ")}`);
|
|
70671
|
+
return `${METADATA_DELIMITER}\n${lines.join("\n")}\n${METADATA_DELIMITER}\n\n${body}`;
|
|
70672
|
+
}
|
|
70673
|
+
/**
|
|
70674
|
+
* Split a rule file into its metadata block and body. The plugin locates the
|
|
70675
|
+
* block with `^---\s*([\s\S]*?)---\s*` (anchored at the very start of the
|
|
70676
|
+
* file); this reads it line by line instead — a delimiter is a line that is
|
|
70677
|
+
* exactly `---` — which is linear on any input and tolerates a BOM or blank
|
|
70678
|
+
* lines before the block. Each line is split on its first colon and trimmed;
|
|
70679
|
+
* unknown fields are ignored; a missing block or `apply:` line yields no
|
|
70680
|
+
* metadata, while an unrecognized `apply:` value is kept verbatim.
|
|
70681
|
+
*/
|
|
70682
|
+
static parseFileContent(fileContent) {
|
|
70683
|
+
const content = fileContent.replace(/^\uFEFF/, "").trimStart();
|
|
70684
|
+
const lines = content.split(LINE_SPLIT_REGEX);
|
|
70685
|
+
const closingIndex = lines[0]?.trim() === METADATA_DELIMITER ? lines.findIndex((line, index) => index > 0 && line.trim() === METADATA_DELIMITER) : -1;
|
|
70686
|
+
if (closingIndex === -1) return {
|
|
70687
|
+
metadata: void 0,
|
|
70688
|
+
body: content.trim()
|
|
70689
|
+
};
|
|
70690
|
+
const body = lines.slice(closingIndex + 1).join("").trim();
|
|
70691
|
+
const fields = /* @__PURE__ */ new Map();
|
|
70692
|
+
for (const line of lines.slice(1, closingIndex)) {
|
|
70693
|
+
const separatorIndex = line.indexOf(":");
|
|
70694
|
+
if (separatorIndex === -1) continue;
|
|
70695
|
+
fields.set(line.slice(0, separatorIndex).trim(), line.slice(separatorIndex + 1).trim());
|
|
70696
|
+
}
|
|
70697
|
+
const apply = fields.get("apply");
|
|
70698
|
+
if (apply === void 0 || apply.length === 0) return {
|
|
70699
|
+
metadata: void 0,
|
|
70700
|
+
body
|
|
70701
|
+
};
|
|
70702
|
+
const instructions = fields.get("instructions");
|
|
70703
|
+
const patterns = fields.get("patterns");
|
|
70704
|
+
return {
|
|
70705
|
+
metadata: {
|
|
70706
|
+
apply,
|
|
70707
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
70708
|
+
...apply === "by file patterns" && patterns && { patterns: splitBraceAwareList(patterns) }
|
|
70709
|
+
},
|
|
70710
|
+
body
|
|
70711
|
+
};
|
|
70712
|
+
}
|
|
70713
|
+
/**
|
|
70714
|
+
* Derive the metadata block from the canonical frontmatter, mirroring the
|
|
70715
|
+
* Cursor mapping: an explicit `aiassistant.apply` wins; otherwise specific
|
|
70716
|
+
* `globs` become `by file patterns`, a `description` on a rule with no
|
|
70717
|
+
* `globs` at all becomes the `by model decision` condition, and everything
|
|
70718
|
+
* else — the root rule, universal globs (which already say "every file",
|
|
70719
|
+
* whatever the description), bare rules — is `always`.
|
|
70720
|
+
*
|
|
70721
|
+
* The plugin splits `patterns` on every comma, so a brace alternation such
|
|
70722
|
+
* as `*.{ts,tsx}` is expanded into one glob per branch before it is written.
|
|
70723
|
+
*/
|
|
70724
|
+
static buildMetadata(frontmatter) {
|
|
70725
|
+
const globs = (frontmatter.globs ?? []).map((glob) => flattenValue(glob)).filter((glob) => glob.length > 0).flatMap((glob) => expandBraceAlternations(glob));
|
|
70726
|
+
const hasUniversalGlob = globs.some((glob) => UNIVERSAL_GLOBS$2.has(glob));
|
|
70727
|
+
const instructions = frontmatter.description && flattenValue(frontmatter.description);
|
|
70728
|
+
const explicitApply = frontmatter.aiassistant?.apply && flattenValue(frontmatter.aiassistant.apply);
|
|
70729
|
+
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";
|
|
70730
|
+
const patterns = globs;
|
|
70731
|
+
return {
|
|
70732
|
+
apply,
|
|
70733
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
70734
|
+
...apply === "by file patterns" && patterns.length > 0 && { patterns }
|
|
70735
|
+
};
|
|
70736
|
+
}
|
|
70737
|
+
/**
|
|
70738
|
+
* Warn, at generate time, about a block the plugin cannot act on as the
|
|
70739
|
+
* author presumably intended.
|
|
70740
|
+
*/
|
|
70741
|
+
static warnAboutMetadata({ metadata, relativeFilePath }) {
|
|
70742
|
+
const { apply, instructions, patterns = [] } = metadata;
|
|
70743
|
+
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.`);
|
|
70744
|
+
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.`);
|
|
70745
|
+
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.`);
|
|
70746
|
+
const commaGlob = patterns.find((glob) => glob.includes(","));
|
|
70747
|
+
if (commaGlob !== void 0) warnWithFallback(void 0, `${relativeFilePath}: the glob "${commaGlob}" contains a comma, which AI Assistant reads as a pattern separator.`);
|
|
70748
|
+
if ([
|
|
70749
|
+
apply,
|
|
70750
|
+
instructions ?? "",
|
|
70751
|
+
...patterns
|
|
70752
|
+
].some((value) => value.includes("---"))) warnWithFallback(void 0, `${relativeFilePath}: a metadata value contains "---", which AI Assistant reads as the end of the metadata block.`);
|
|
70753
|
+
}
|
|
69871
70754
|
toRulesyncRule() {
|
|
69872
|
-
|
|
70755
|
+
const metadata = this.metadata;
|
|
70756
|
+
const globs = metadata?.apply === "by file patterns" ? metadata.patterns ?? [] : metadata?.apply === "always" ? ["**/*"] : [];
|
|
70757
|
+
const frontmatter = {
|
|
70758
|
+
root: false,
|
|
70759
|
+
targets: ["*"],
|
|
70760
|
+
description: metadata?.apply === "by model decision" ? metadata.instructions : void 0,
|
|
70761
|
+
globs
|
|
70762
|
+
};
|
|
70763
|
+
const derivedApply = metadata === void 0 ? void 0 : AiassistantRule.buildMetadata(frontmatter).apply;
|
|
70764
|
+
const apply = metadata !== void 0 && derivedApply !== metadata.apply ? metadata.apply : void 0;
|
|
70765
|
+
return new RulesyncRule({
|
|
70766
|
+
outputRoot: process.cwd(),
|
|
70767
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
70768
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
70769
|
+
frontmatter: {
|
|
70770
|
+
...frontmatter,
|
|
70771
|
+
...apply !== void 0 && { aiassistant: { apply } }
|
|
70772
|
+
},
|
|
70773
|
+
body: this.body
|
|
70774
|
+
});
|
|
69873
70775
|
}
|
|
69874
70776
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
70777
|
+
const metadata = this.buildMetadata(rulesyncRule.getFrontmatter());
|
|
70778
|
+
this.warnAboutMetadata({
|
|
70779
|
+
metadata,
|
|
70780
|
+
relativeFilePath: rulesyncRule.getRelativeFilePath()
|
|
70781
|
+
});
|
|
69875
70782
|
return new AiassistantRule({
|
|
69876
70783
|
outputRoot,
|
|
69877
70784
|
relativeDirPath: this.getSettablePaths().nonRoot.relativeDirPath,
|
|
69878
70785
|
relativeFilePath: rulesyncRule.getRelativeFilePath(),
|
|
69879
|
-
|
|
70786
|
+
body: rulesyncRule.getBody(),
|
|
70787
|
+
metadata,
|
|
69880
70788
|
validate,
|
|
69881
70789
|
root: false
|
|
69882
70790
|
});
|
|
@@ -69884,11 +70792,13 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
69884
70792
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
69885
70793
|
const relativeDirPath = this.getSettablePaths().nonRoot.relativeDirPath;
|
|
69886
70794
|
const fileContent = await readFileContent(join(outputRoot, relativeDirPath, relativeFilePath));
|
|
70795
|
+
const { metadata, body } = this.parseFileContent(fileContent);
|
|
69887
70796
|
return new AiassistantRule({
|
|
69888
70797
|
outputRoot,
|
|
69889
70798
|
relativeDirPath,
|
|
69890
70799
|
relativeFilePath,
|
|
69891
|
-
|
|
70800
|
+
body,
|
|
70801
|
+
metadata,
|
|
69892
70802
|
validate,
|
|
69893
70803
|
root: false
|
|
69894
70804
|
});
|
|
@@ -69898,7 +70808,7 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
69898
70808
|
outputRoot,
|
|
69899
70809
|
relativeDirPath,
|
|
69900
70810
|
relativeFilePath,
|
|
69901
|
-
|
|
70811
|
+
body: "",
|
|
69902
70812
|
validate: false,
|
|
69903
70813
|
root: false
|
|
69904
70814
|
});
|
|
@@ -71455,40 +72365,14 @@ const CodebuddyRuleFrontmatterSchema = z.object({
|
|
|
71455
72365
|
enabled: z.optional(z.boolean())
|
|
71456
72366
|
});
|
|
71457
72367
|
/**
|
|
71458
|
-
* Splits a scalar `paths` value on the commas that separate patterns, leaving
|
|
71459
|
-
* the commas inside a brace group alone: CodeBuddy's memory docs say "You can
|
|
71460
|
-
* also combine multiple patterns with commas" and give
|
|
71461
|
-
* `paths: {src,lib}/**\/*.ts, tests/**\/*.test.ts` as the example, where the
|
|
71462
|
-
* first comma belongs to the brace expansion and the second separates the two
|
|
71463
|
-
* globs. Only a scalar is split — the docs describe the comma form for the
|
|
71464
|
-
* string shape, and a list already separates its patterns.
|
|
71465
|
-
*/
|
|
71466
|
-
function splitCodebuddyPathsScalar(paths) {
|
|
71467
|
-
const patterns = [];
|
|
71468
|
-
let current = "";
|
|
71469
|
-
let braceDepth = 0;
|
|
71470
|
-
for (const char of paths) {
|
|
71471
|
-
if (char === "{") braceDepth += 1;
|
|
71472
|
-
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
71473
|
-
else if (char === "," && braceDepth === 0) {
|
|
71474
|
-
patterns.push(current);
|
|
71475
|
-
current = "";
|
|
71476
|
-
continue;
|
|
71477
|
-
}
|
|
71478
|
-
current += char;
|
|
71479
|
-
}
|
|
71480
|
-
patterns.push(current);
|
|
71481
|
-
return patterns.map((pattern) => pattern.trim());
|
|
71482
|
-
}
|
|
71483
|
-
/**
|
|
71484
72368
|
* Normalizes the documented `string` / `string[]` shapes of `paths` to the
|
|
71485
72369
|
* list form the rest of the adapter works with, splitting a comma-separated
|
|
71486
|
-
* scalar into its patterns (see `
|
|
72370
|
+
* scalar into its patterns (see `splitBraceAwareList`). An empty list
|
|
71487
72371
|
* and an empty string both mean "no paths".
|
|
71488
72372
|
*/
|
|
71489
72373
|
function normalizeCodebuddyPaths(paths) {
|
|
71490
72374
|
if (paths === void 0) return;
|
|
71491
|
-
const list = (typeof paths === "string" ?
|
|
72375
|
+
const list = (typeof paths === "string" ? splitBraceAwareList(paths) : paths).filter((path) => path.trim() !== "");
|
|
71492
72376
|
return list.length > 0 ? list : void 0;
|
|
71493
72377
|
}
|
|
71494
72378
|
/**
|
|
@@ -79705,4 +80589,4 @@ async function importChecksCore(params) {
|
|
|
79705
80589
|
//#endregion
|
|
79706
80590
|
export { RulesyncCommand as $, toPosixPath as $t, CLAUDECODE_MEMORIES_DIR_NAME as A, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as An, checkPathTraversal as At, RulesyncSubagent as B, stripControlCharacters as Bn, listDirectoryEntryNames as Bt, COMMANDCODE_DIR as C, RULESYNC_PERMISSIONS_SCHEMA_URL as Cn, withWarnOnceScope as Ct, CODEXCLI_DIR as D, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Dn, assertDirectoryIfExists as Dt, CODEXCLI_BASH_RULES_FILE_NAME as E, RULESYNC_SKILLS_RELATIVE_DIR_PATH as En, applyFileMode as Et, caseFoldIdentity as F, formatError as Fn, getFileSize as Ft, RulesyncRuleFrontmatterSchema as G, readFileContentOrNull as Gt, RulesyncSkill as H, stripHiddenCharacters as Hn, listSubdirectoryNames as Ht, groupSpellingsByCaseFoldedIdentity as I, truncateText as In, getHomeDirectory as It, RulesyncIgnore as J, removeFile as Jt, RulesyncPermissions as K, removeDirectory as Kt, AUGMENTCODE_DIR as L, hasDeceptiveHiddenCharacters as Ln, isFileNotFoundError as Lt, CLAUDECODE_SKILLS_DIR_PATH as M, ALL_FEATURES as Mn, directoryExists as Mt, FACTORYDROID_DIR as N, ALL_FEATURES_WITH_WILDCARD as Nn, ensureDir as Nt, CLAUDECODE_DIR as O, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as On, assertTreeContainsNoSymlinks as Ot, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as P, DEPRECATED_FEATURE_REPLACEMENTS as Pn, fileExists as Pt, parseJsonc as Q, runWithDirectoryRollback as Qt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as R, hasEnclosingMarkOutsideKeycap as Rn, isFileSystemError as Rt, QWENCODE_LOCAL_RULE_FILE_NAME as S, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Sn, resetRunWarningState as St, ChecksProcessor as T, RULESYNC_RULES_RELATIVE_DIR_PATH as Tn, ErrorCodes as Tt, RulesyncSkillFrontmatterSchema as U, pathEscapesRoot as Ut, RulesyncSubagentFrontmatterSchema as V, stripControlCharactersKeepingLineFeeds as Vn, listFilePathsRecursively as Vt, RulesyncRule as W, readFileContent as Wt, getRulesyncSourceCandidates as X, removeTempDirectory as Xt, RulesyncHooks as Y, removeFileStrict as Yt, resolveRulesyncSourceWritePath as Z, resolvePath as Zt, IgnoreProcessor as _, RULESYNC_IGNORE_RELATIVE_FILE_PATH as _n, JsonLogger as _t, getProcessorRegistryEntry as a, ToolTargetSchema as an, SHARED_USER_MANAGED_CONFIG_PATHS as at, CommandsProcessor as b, RULESYNC_MCP_SCHEMA_URL as bn, warnOnConflictingFlags as bt, RulesProcessor as c, MAX_FILE_SIZE as cn, mergeInputRootConfigs as ct, CODEBUDDY_DIR as d, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as dn, ConfigFileSchema as dt, writeFileBuffer as en, RulesyncCommandFrontmatterSchema as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_CONFIG_RELATIVE_FILE_PATH as fn, GITIGNORE_DESTINATION_KEY as ft, McpProcessor as g, RULESYNC_HOOKS_RELATIVE_FILE_PATH as gn, ConsoleLogger as gt, shortenToWidth as h, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as hn, findControlCharacter as ht, inspectInputRoots as i, PACKAGING_TOOL_TARGETS as in, loadYaml as it, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as j, parseCommaSeparatedList as jn, createTempDirectory as jt, CLAUDECODE_LOCAL_RULE_FILE_NAME as k, RULESYNC_USER_CONFIG_DIR_NAME as kn, assertWritablePathInsideRoot as kt, SubagentsProcessor as l, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ln, resolveEffectiveInputRoots as lt, displayWidthOf as m, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as mn, assertTargetsFeaturesExclusive as mt, formatSourceLoadFailure as n, ALL_TOOL_TARGETS as nn, RulesyncCheckFrontmatterSchema as nt, convertFromTool as o, CURATED_RULES_FEATURE_SUBDIR as on, SKILL_FILE_NAME as ot, ELLIPSIS_WIDTH as p, RULESYNC_CONFIG_SCHEMA_URL as pn, SourceEntrySchema as pt, RulesyncMcp as q, removeDirectoryStrict as qt, generate as r, ALL_TOOL_TARGETS_WITH_WILDCARD as rn, stringifyFrontmatter as rt, isPackagingToolTarget as s, FEATURE_SOURCE_TREE_ENTRIES as sn, ConfigResolver as st, importFromTool as t, writeFileContent as tn, RulesyncCheck as tt, SkillsProcessor as u, RULESYNC_CHECKS_RELATIVE_DIR_PATH as un, CONFLICTING_TARGET_PAIRS as ut, HooksProcessor as v, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as vn, WarningCollectingLogger as vt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as w, RULESYNC_RELATIVE_DIR_PATH as wn, CLIError as wt, QWENCODE_DIR as x, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as xn, withFallbackLoggerTarget as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_MCP_RELATIVE_FILE_PATH as yn, fallbackLogger as yt, getLocalSkillDirNames as z, quoteForLog as zn, isSymlink as zt };
|
|
79707
80591
|
|
|
79708
|
-
//# sourceMappingURL=import-
|
|
80592
|
+
//# sourceMappingURL=import-BnF2J75x.js.map
|