rulesync 16.9.1 → 16.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/cli/index.cjs +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BaRDaZET.cjs → import--_2cfZlH.cjs} +1843 -164
- package/dist/{import-BpKoN2US.js → import-Bgkf_jVN.js} +1829 -150
- package/dist/import-Bgkf_jVN.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BpKoN2US.js.map +0 -1
|
@@ -171,6 +171,7 @@ const rulesProcessorToolTargetTuple = [
|
|
|
171
171
|
"kiro",
|
|
172
172
|
"kiro-cli",
|
|
173
173
|
"kiro-ide",
|
|
174
|
+
"musecode",
|
|
174
175
|
"opencode",
|
|
175
176
|
"pi",
|
|
176
177
|
"qwencode",
|
|
@@ -234,6 +235,7 @@ const mcpProcessorToolTargetTuple = [
|
|
|
234
235
|
"kiro-cli",
|
|
235
236
|
"kiro-ide",
|
|
236
237
|
"junie",
|
|
238
|
+
"musecode",
|
|
237
239
|
"opencode",
|
|
238
240
|
"qwencode",
|
|
239
241
|
"reasonix",
|
|
@@ -341,6 +343,7 @@ const skillsProcessorToolTargetTuple = [
|
|
|
341
343
|
"kiro",
|
|
342
344
|
"kiro-cli",
|
|
343
345
|
"kiro-ide",
|
|
346
|
+
"musecode",
|
|
344
347
|
"opencode",
|
|
345
348
|
"pi",
|
|
346
349
|
"qwencode",
|
|
@@ -395,6 +398,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
395
398
|
"cline",
|
|
396
399
|
"codexcli",
|
|
397
400
|
"copilot",
|
|
401
|
+
"copilotcli",
|
|
398
402
|
"cursor",
|
|
399
403
|
"devin",
|
|
400
404
|
"factorydroid",
|
|
@@ -418,6 +422,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
418
422
|
];
|
|
419
423
|
const checksProcessorToolTargetTuple = [
|
|
420
424
|
"amp",
|
|
425
|
+
"augmentcode",
|
|
421
426
|
"cursor",
|
|
422
427
|
"hermesagent",
|
|
423
428
|
"rovodev",
|
|
@@ -1730,6 +1735,8 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
1730
1735
|
".claude/settings.json",
|
|
1731
1736
|
".claude/settings.local.json",
|
|
1732
1737
|
".codex/config.toml",
|
|
1738
|
+
".copilot/settings.json",
|
|
1739
|
+
".github/copilot/settings.json",
|
|
1733
1740
|
".devin/config.json",
|
|
1734
1741
|
".factory/settings.json",
|
|
1735
1742
|
".grok/config.toml",
|
|
@@ -2469,14 +2476,23 @@ const CLINE_HOOK_EVENTS = [
|
|
|
2469
2476
|
/**
|
|
2470
2477
|
* Hook events supported by GitHub Copilot (cloud coding agent).
|
|
2471
2478
|
*
|
|
2472
|
-
*
|
|
2479
|
+
* The events rulesync writes to `.github/hooks/*.json`:
|
|
2473
2480
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted` ← `beforeSubmitPrompt`,
|
|
2474
|
-
* `preToolUse`, `postToolUse`, `agentStop` ← `stop`, `
|
|
2475
|
-
* `errorOccurred` ← `afterError
|
|
2476
|
-
* not part of the documented cloud-agent surface.
|
|
2481
|
+
* `preToolUse`, `postToolUse`, `agentStop` ← `stop`, `subagentStart`,
|
|
2482
|
+
* `subagentStop`, `errorOccurred` ← `afterError`, and `preCompact`.
|
|
2477
2483
|
*
|
|
2478
|
-
*
|
|
2479
|
-
*
|
|
2484
|
+
* `preCompact` and `subagentStart` are authorable because the unified hooks
|
|
2485
|
+
* reference's per-event "Cloud agent" column says both fire there. That column
|
|
2486
|
+
* is the authority for this set: the older cloud-agent concept page still
|
|
2487
|
+
* lists only the eight events this set began as, and re-narrowing to it would
|
|
2488
|
+
* undo that. `notification` and `permissionRequest` stay out because the same
|
|
2489
|
+
* column is explicit that they do not fire on the cloud agent.
|
|
2490
|
+
*
|
|
2491
|
+
* Two further CLI events — `postToolUseFailure` and `userPromptTransformed`
|
|
2492
|
+
* ({@link COPILOTCLI_HOOK_EVENTS}) — are documented as firing on the cloud
|
|
2493
|
+
* agent as well but are not modelled here yet.
|
|
2494
|
+
*
|
|
2495
|
+
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
2480
2496
|
*/
|
|
2481
2497
|
const COPILOT_HOOK_EVENTS = [
|
|
2482
2498
|
"sessionStart",
|
|
@@ -2485,8 +2501,10 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
2485
2501
|
"preToolUse",
|
|
2486
2502
|
"postToolUse",
|
|
2487
2503
|
"stop",
|
|
2504
|
+
"subagentStart",
|
|
2488
2505
|
"subagentStop",
|
|
2489
|
-
"afterError"
|
|
2506
|
+
"afterError",
|
|
2507
|
+
"preCompact"
|
|
2490
2508
|
];
|
|
2491
2509
|
/**
|
|
2492
2510
|
* Hook events supported by the GitHub Copilot CLI (`copilotcli-hooks.ts`).
|
|
@@ -2588,8 +2606,8 @@ const CODEXCLI_HOOK_EVENTS = [
|
|
|
2588
2606
|
* has NO `SubagentStart`/`SubagentStop` arms — emitting them would write keys
|
|
2589
2607
|
* Goose silently ignores, so `subagentStart`/`subagentStop` are intentionally
|
|
2590
2608
|
* excluded here and from `CANONICAL_TO_GOOSE_EVENT_NAMES`.
|
|
2591
|
-
* @see https://github.com/
|
|
2592
|
-
* @see https://
|
|
2609
|
+
* @see https://github.com/aaif-goose/goose/blob/v1.41.0/crates/goose/src/hooks/mod.rs
|
|
2610
|
+
* @see https://goose-docs.ai/docs/guides/context-engineering/hooks/
|
|
2593
2611
|
*/
|
|
2594
2612
|
const GOOSE_HOOK_EVENTS = [
|
|
2595
2613
|
"sessionStart",
|
|
@@ -2626,8 +2644,8 @@ const KIRO_HOOK_EVENTS = [
|
|
|
2626
2644
|
* `UserPromptSubmit`, `PreToolUse`, and `PostToolUse`. Kiro also documents
|
|
2627
2645
|
* file-event (`PostFileCreate`/`PostFileSave`/`PostFileDelete`) and spec-task
|
|
2628
2646
|
* (`PreTaskExec`/`PostTaskExec`) triggers that have no canonical equivalent;
|
|
2629
|
-
* those can still be emitted verbatim via
|
|
2630
|
-
*
|
|
2647
|
+
* those can still be emitted verbatim via the shared `kiro` override block
|
|
2648
|
+
* (unknown event keys pass through unchanged). There is no `SessionEnd`
|
|
2631
2649
|
* trigger, so the canonical `sessionEnd` has no home here.
|
|
2632
2650
|
* @see https://kiro.dev/docs/hooks/types/
|
|
2633
2651
|
*/
|
|
@@ -3222,8 +3240,10 @@ const CANONICAL_TO_COPILOT_EVENT_NAMES = {
|
|
|
3222
3240
|
preToolUse: "preToolUse",
|
|
3223
3241
|
postToolUse: "postToolUse",
|
|
3224
3242
|
stop: "agentStop",
|
|
3243
|
+
subagentStart: "subagentStart",
|
|
3225
3244
|
subagentStop: "subagentStop",
|
|
3226
|
-
afterError: "errorOccurred"
|
|
3245
|
+
afterError: "errorOccurred",
|
|
3246
|
+
preCompact: "preCompact"
|
|
3227
3247
|
};
|
|
3228
3248
|
/**
|
|
3229
3249
|
* Map Copilot camelCase event names to canonical camelCase.
|
|
@@ -3360,6 +3380,47 @@ const CANONICAL_TO_KIRO_EVENT_NAMES = {
|
|
|
3360
3380
|
stop: "stop"
|
|
3361
3381
|
};
|
|
3362
3382
|
/**
|
|
3383
|
+
* Native event keys of the embedded agent-config hook format, as listed by the
|
|
3384
|
+
* Kiro CLI 3.0 migration guide's old-format column: `agentSpawn`,
|
|
3385
|
+
* `userPromptSubmit`, `preToolUse`, `postToolUse`, `fileEdited`, `fileCreated`
|
|
3386
|
+
* and `agentStop`/`stop`. Two of them (`fileEdited`, `fileCreated`) have no
|
|
3387
|
+
* canonical equivalent, so they exist only here.
|
|
3388
|
+
*
|
|
3389
|
+
* The `kiro` override block is shared with the standalone-format targets, whose
|
|
3390
|
+
* trigger vocabulary is different, so the alias writer uses this list to decide
|
|
3391
|
+
* what it can express.
|
|
3392
|
+
* @see https://kiro.dev/docs/cli/v3/hooks-migration/
|
|
3393
|
+
*/
|
|
3394
|
+
const KIRO_AGENT_CONFIG_NATIVE_EVENT_NAMES = [
|
|
3395
|
+
"agentSpawn",
|
|
3396
|
+
"userPromptSubmit",
|
|
3397
|
+
"preToolUse",
|
|
3398
|
+
"postToolUse",
|
|
3399
|
+
"fileEdited",
|
|
3400
|
+
"fileCreated",
|
|
3401
|
+
"agentStop",
|
|
3402
|
+
"stop"
|
|
3403
|
+
];
|
|
3404
|
+
/**
|
|
3405
|
+
* Old agent-config event keys mapped to their standalone v1 trigger, so a
|
|
3406
|
+
* `kiro.hooks` block authored in the deprecated spelling still emits a valid
|
|
3407
|
+
* trigger for the `kiro-cli` / `kiro-ide` targets that read the same block.
|
|
3408
|
+
* The migration guide states the old names "map directly to their newer
|
|
3409
|
+
* equivalents" and spells out `agentSpawn` → `SessionStart` and `fileEdited` →
|
|
3410
|
+
* `PostFileSave`; the rest are the same event under the v1 casing.
|
|
3411
|
+
* @see https://kiro.dev/docs/cli/v3/hooks-migration/
|
|
3412
|
+
*/
|
|
3413
|
+
const KIRO_LEGACY_TO_KIRO_IDE_TRIGGER_NAMES = {
|
|
3414
|
+
agentSpawn: "SessionStart",
|
|
3415
|
+
userPromptSubmit: "UserPromptSubmit",
|
|
3416
|
+
preToolUse: "PreToolUse",
|
|
3417
|
+
postToolUse: "PostToolUse",
|
|
3418
|
+
fileEdited: "PostFileSave",
|
|
3419
|
+
fileCreated: "PostFileCreate",
|
|
3420
|
+
agentStop: "Stop",
|
|
3421
|
+
stop: "Stop"
|
|
3422
|
+
};
|
|
3423
|
+
/**
|
|
3363
3424
|
* Map Kiro CLI camelCase event names to canonical camelCase.
|
|
3364
3425
|
*/
|
|
3365
3426
|
const KIRO_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_KIRO_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
@@ -3367,8 +3428,8 @@ const KIRO_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICA
|
|
|
3367
3428
|
* Map canonical camelCase event names to Kiro IDE PascalCase triggers.
|
|
3368
3429
|
*
|
|
3369
3430
|
* Only the canonical lifecycle events with a clean IDE equivalent are mapped.
|
|
3370
|
-
* Unknown keys (e.g. IDE-only `PostFileSave`/`PreTaskExec` set via
|
|
3371
|
-
* override) pass through unchanged.
|
|
3431
|
+
* Unknown keys (e.g. IDE-only `PostFileSave`/`PreTaskExec` set via the shared
|
|
3432
|
+
* `kiro` override block) pass through unchanged.
|
|
3372
3433
|
* @see https://kiro.dev/docs/hooks/types/
|
|
3373
3434
|
*/
|
|
3374
3435
|
const CANONICAL_TO_KIRO_IDE_EVENT_NAMES = {
|
|
@@ -4869,13 +4930,57 @@ const CodexcliPermissionsOverrideSchema = z.looseObject({
|
|
|
4869
4930
|
git_write_rules: z.optional(z.boolean())
|
|
4870
4931
|
});
|
|
4871
4932
|
/**
|
|
4933
|
+
* Tool-scoped override block for Zed. Two Zed surfaces sit outside the canonical
|
|
4934
|
+
* allow/ask/deny model that `agent.tool_permissions` implements, and both are
|
|
4935
|
+
* authored here verbatim:
|
|
4936
|
+
*
|
|
4937
|
+
* - `sandbox_permissions` — the OS-level agent sandbox, on by default since Zed
|
|
4938
|
+
* 1.14.2 for the `terminal` and `fetch` tools. Its defaults forbid network
|
|
4939
|
+
* access, writing outside the project directories, and writing to `.git`, so a
|
|
4940
|
+
* real setup usually needs to relax one of them (`network_hosts` with exact
|
|
4941
|
+
* hostnames or leading `*.` wildcards, `allow_all_hosts`, `write_paths`,
|
|
4942
|
+
* `allow_fs_write_all`, `allow_unsandboxed`). This is containment, not tool
|
|
4943
|
+
* gating: no canonical category expresses it, so the block is written straight
|
|
4944
|
+
* into `agent.sandbox_permissions`, mirroring the Kilo / Claude Code / Codex
|
|
4945
|
+
* CLI `sandbox` passthrough precedent. Kept a bare `looseObject` rather than
|
|
4946
|
+
* enumerating the keys, since upstream adds to them release over release.
|
|
4947
|
+
* - `profiles` — the tool-availability layer, a separate enforcement stage from
|
|
4948
|
+
* `tool_permissions`: a tool absent from the active profile cannot be used no
|
|
4949
|
+
* matter what the permission rules say. Per-profile keys are `name`, `tools`
|
|
4950
|
+
* (per-tool booleans), `enable_all_context_servers`, `context_servers` and
|
|
4951
|
+
* `default_model`. Like Kimi Code's `tools.enabled`/`disabled` block this is a
|
|
4952
|
+
* verbatim passthrough — no canonicalization is attempted.
|
|
4953
|
+
*
|
|
4954
|
+
* Neither surface can weaken a canonical deny, because `agent.tool_permissions`
|
|
4955
|
+
* is off-limits to this override: the translator consumes only the two keys
|
|
4956
|
+
* above by name and rejects a `tool_permissions` key with a warning.
|
|
4957
|
+
*
|
|
4958
|
+
* @see https://zed.dev/docs/ai/sandboxing
|
|
4959
|
+
* @see https://zed.dev/docs/ai/agent-profiles
|
|
4960
|
+
*
|
|
4961
|
+
* @example
|
|
4962
|
+
* { "sandbox_permissions": { "network_hosts": ["*.github.com"], "write_paths": ["/tmp"] },
|
|
4963
|
+
* "profiles": { "review": { "name": "Review", "tools": { "terminal": false } } } }
|
|
4964
|
+
*/
|
|
4965
|
+
const ZedPermissionsOverrideSchema = z.looseObject({
|
|
4966
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
4967
|
+
sandbox_permissions: z.optional(z.looseObject({})),
|
|
4968
|
+
profiles: z.optional(z.record(z.string(), z.looseObject({
|
|
4969
|
+
name: z.optional(z.string()),
|
|
4970
|
+
tools: z.optional(z.record(z.string(), z.boolean())),
|
|
4971
|
+
enable_all_context_servers: z.optional(z.boolean()),
|
|
4972
|
+
context_servers: z.optional(z.looseObject({})),
|
|
4973
|
+
default_model: z.optional(z.looseObject({}))
|
|
4974
|
+
})))
|
|
4975
|
+
});
|
|
4976
|
+
/**
|
|
4872
4977
|
* Permissions configuration.
|
|
4873
4978
|
* Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
|
|
4874
4979
|
* Values are pattern-to-action mappings for that tool category.
|
|
4875
4980
|
*
|
|
4876
4981
|
* The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
|
|
4877
4982
|
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie`/`takt`/`amp`/
|
|
4878
|
-
* `antigravity-cli`/`augmentcode`/`kiro`/`codexcli` keys are tool-scoped
|
|
4983
|
+
* `antigravity-cli`/`augmentcode`/`kiro`/`codexcli`/`zed` keys are tool-scoped
|
|
4879
4984
|
* overrides consumed only by their respective translator (see the matching
|
|
4880
4985
|
* `*PermissionsOverrideSchema`); every other tool reads the shared `permission`
|
|
4881
4986
|
* block and ignores them.
|
|
@@ -4915,14 +5020,15 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
4915
5020
|
augmentcode: z.optional(AugmentcodePermissionsOverrideSchema),
|
|
4916
5021
|
kiro: z.optional(KiroPermissionsOverrideSchema),
|
|
4917
5022
|
codexcli: z.optional(CodexcliPermissionsOverrideSchema),
|
|
5023
|
+
zed: z.optional(ZedPermissionsOverrideSchema),
|
|
4918
5024
|
"antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
|
|
4919
5025
|
copilot: z.optional(CanonicalPermissionsOverrideSchema),
|
|
5026
|
+
copilotcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
4920
5027
|
devin: z.optional(CanonicalPermissionsOverrideSchema),
|
|
4921
5028
|
goose: z.optional(CanonicalPermissionsOverrideSchema),
|
|
4922
5029
|
grokcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
4923
5030
|
"kimi-code": z.optional(KimiCodePermissionsOverrideSchema),
|
|
4924
|
-
rovodev: z.optional(CanonicalPermissionsOverrideSchema)
|
|
4925
|
-
zed: z.optional(CanonicalPermissionsOverrideSchema)
|
|
5031
|
+
rovodev: z.optional(CanonicalPermissionsOverrideSchema)
|
|
4926
5032
|
});
|
|
4927
5033
|
/**
|
|
4928
5034
|
* Full permissions file schema including optional $schema field.
|
|
@@ -5635,6 +5741,33 @@ function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
|
5635
5741
|
return section?.["user-invocable"] ?? rootFrontmatter["user-invocable"];
|
|
5636
5742
|
}
|
|
5637
5743
|
//#endregion
|
|
5744
|
+
//#region src/constants/augmentcode-paths.ts
|
|
5745
|
+
const AUGMENTCODE_DIR = ".augment";
|
|
5746
|
+
const AUGMENTCODE_COMMANDS_DIR_PATH = join(AUGMENTCODE_DIR, "commands");
|
|
5747
|
+
/**
|
|
5748
|
+
* The cross-tool `.agents/commands/` root Auggie also discovers commands from.
|
|
5749
|
+
* Auggie resolves commands over `.augment`, `.claude` and `.agents` in both the
|
|
5750
|
+
* workspace and the home directory; rulesync writes to its own `.augment` root
|
|
5751
|
+
* and reads this one as well, mirroring how skills treat `.agents/skills`.
|
|
5752
|
+
* @see https://docs.augmentcode.com/cli/custom-commands
|
|
5753
|
+
*/
|
|
5754
|
+
const AUGMENTCODE_AGENTS_COMMANDS_DIR_PATH = join(".agents", "commands");
|
|
5755
|
+
const AUGMENTCODE_SKILLS_DIR_PATH = join(AUGMENTCODE_DIR, "skills");
|
|
5756
|
+
const AUGMENTCODE_AGENTS_DIR_PATH = join(AUGMENTCODE_DIR, "agents");
|
|
5757
|
+
const AUGMENTCODE_ALT_AGENTS_DIR_PATH = ".agents";
|
|
5758
|
+
const AUGMENTCODE_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
5759
|
+
const AUGMENTCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
5760
|
+
const AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME = "settings.local.json";
|
|
5761
|
+
/**
|
|
5762
|
+
* Augment Code Review's custom guidelines file. Read from the repository root's
|
|
5763
|
+
* `.augment/` folder, project scope only — Augment documents no user-level
|
|
5764
|
+
* equivalent, and the reviewer runs against the committed tree.
|
|
5765
|
+
* @see https://docs.augmentcode.com/codereview/review-guidelines
|
|
5766
|
+
*/
|
|
5767
|
+
const AUGMENTCODE_CODE_REVIEW_GUIDELINES_FILE_NAME = "code_review_guidelines.yaml";
|
|
5768
|
+
const AUGMENTCODE_IGNORE_FILE_NAME = ".augmentignore";
|
|
5769
|
+
const AUGMENTCODE_LEGACY_RULE_FILE_NAME = ".augment-guidelines";
|
|
5770
|
+
//#endregion
|
|
5638
5771
|
//#region src/constants/cursor-paths.ts
|
|
5639
5772
|
const CURSOR_DIR = ".cursor";
|
|
5640
5773
|
const CURSOR_COMMANDS_DIR_PATH = join(CURSOR_DIR, "commands");
|
|
@@ -5684,6 +5817,19 @@ const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
|
5684
5817
|
*/
|
|
5685
5818
|
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
5686
5819
|
/**
|
|
5820
|
+
* Takt's runtime provider config (Takt 0.56.0+). Lives at `.takt/runtime.yaml`
|
|
5821
|
+
* (project) and `~/.takt/runtime.yaml` (global), and owns provider/model/
|
|
5822
|
+
* provider-option assignment. Takt generates the global file on first launch,
|
|
5823
|
+
* so most installs have one; "runtime mode" only activates when its `provider:`
|
|
5824
|
+
* section carries an actual assignment.
|
|
5825
|
+
*
|
|
5826
|
+
* rulesync only ever READS this file: it resolves the active provider from it,
|
|
5827
|
+
* and it refuses to write the legacy `provider_options` key into `config.yaml`
|
|
5828
|
+
* while runtime mode is active (Takt hard-fails on that combination).
|
|
5829
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
5830
|
+
*/
|
|
5831
|
+
const TAKT_RUNTIME_CONFIG_FILE_NAME = "runtime.yaml";
|
|
5832
|
+
/**
|
|
5687
5833
|
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
5688
5834
|
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
5689
5835
|
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
@@ -6159,6 +6305,363 @@ function slugifyCheckName(value) {
|
|
|
6159
6305
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
6160
6306
|
}
|
|
6161
6307
|
//#endregion
|
|
6308
|
+
//#region src/features/checks/augmentcode-check.ts
|
|
6309
|
+
/** Augment's severity scale. Narrower than the canonical one. */
|
|
6310
|
+
const AUGMENTCODE_SEVERITIES = [
|
|
6311
|
+
"high",
|
|
6312
|
+
"medium",
|
|
6313
|
+
"low"
|
|
6314
|
+
];
|
|
6315
|
+
/**
|
|
6316
|
+
* Canonical severity to Augment's scale. Augment has no band above `high`, so
|
|
6317
|
+
* canonical `critical` folds into it — the alternative, dropping the rule or
|
|
6318
|
+
* demoting it to `medium`, would either lose the check or understate the one
|
|
6319
|
+
* severity a reviewer most wants raised. The fold is one-way: a `critical`
|
|
6320
|
+
* check generates `high` and imports back as `high`, so the canonical value is
|
|
6321
|
+
* not recoverable from Augment's file alone.
|
|
6322
|
+
*/
|
|
6323
|
+
const CANONICAL_TO_AUGMENTCODE_SEVERITY = {
|
|
6324
|
+
low: "low",
|
|
6325
|
+
medium: "medium",
|
|
6326
|
+
high: "high",
|
|
6327
|
+
critical: "high"
|
|
6328
|
+
};
|
|
6329
|
+
/**
|
|
6330
|
+
* Every field of an `areas.<key>` entry is required upstream, so a check with no
|
|
6331
|
+
* `severity` still has to emit one. `medium` is the neutral middle of Augment's
|
|
6332
|
+
* three bands: defaulting to `high` would inflate every unannotated check past
|
|
6333
|
+
* the ones deliberately marked `medium`, and `low` would bury them.
|
|
6334
|
+
*/
|
|
6335
|
+
const DEFAULT_AUGMENTCODE_SEVERITY = "medium";
|
|
6336
|
+
/**
|
|
6337
|
+
* Augment's own example uses `["**"]`, and `globs` is required per area. Built
|
|
6338
|
+
* fresh per area rather than shared: js-yaml serializes a repeated reference as
|
|
6339
|
+
* an anchor/alias pair, which would litter a file Augment tells users to edit by
|
|
6340
|
+
* hand with `&ref_0` / `*ref_0`.
|
|
6341
|
+
*/
|
|
6342
|
+
const defaultAreaGlobs = () => ["**"];
|
|
6343
|
+
/**
|
|
6344
|
+
* The `augmentcode` block of a check's frontmatter, carrying the two things the
|
|
6345
|
+
* canonical check model has no home for: which area a rule belongs to (so
|
|
6346
|
+
* several checks can share one), and the area's `globs`.
|
|
6347
|
+
*/
|
|
6348
|
+
const AugmentcodeCheckOverrideSchema = z.looseObject({
|
|
6349
|
+
/** Area key this check's rule is grouped under; defaults to the check's slug. */
|
|
6350
|
+
area: z.optional(z.string().check(z.minLength(1))),
|
|
6351
|
+
/** Human-readable `areas.<key>.description`; defaults to the check's own. */
|
|
6352
|
+
areaDescription: z.optional(z.string()),
|
|
6353
|
+
globs: z.optional(z.array(z.string().check(z.minLength(1)))),
|
|
6354
|
+
/** Rule `id`; defaults to the check's slug. */
|
|
6355
|
+
id: z.optional(z.string().check(z.minLength(1)))
|
|
6356
|
+
});
|
|
6357
|
+
function parseOverride$1(raw, filePath, logger) {
|
|
6358
|
+
if (raw === void 0) return {};
|
|
6359
|
+
if (!isPlainObject$1(raw)) {
|
|
6360
|
+
logger?.warn(`Ignoring the \`augmentcode\` block in ${filePath}: expected a mapping.`);
|
|
6361
|
+
return {};
|
|
6362
|
+
}
|
|
6363
|
+
const result = AugmentcodeCheckOverrideSchema.safeParse(raw);
|
|
6364
|
+
if (!result.success) throw new Error(`Invalid \`augmentcode\` block in ${filePath}: ${result.error.message}`, { cause: result.error });
|
|
6365
|
+
return result.data;
|
|
6366
|
+
}
|
|
6367
|
+
function stemOf$1(rulesyncCheck) {
|
|
6368
|
+
return basename(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
6369
|
+
}
|
|
6370
|
+
/**
|
|
6371
|
+
* Disambiguate a name against the ones already taken. Used for rule ids on
|
|
6372
|
+
* generate (two same-named checks in different subdirectories) and for check
|
|
6373
|
+
* file names on import (one rule id repeated across two areas), since a
|
|
6374
|
+
* collision in either direction silently loses one of the pair.
|
|
6375
|
+
*/
|
|
6376
|
+
function uniqueName(preferred, used) {
|
|
6377
|
+
let name = preferred;
|
|
6378
|
+
let suffix = 2;
|
|
6379
|
+
while (used.has(name)) {
|
|
6380
|
+
name = `${preferred}-${suffix}`;
|
|
6381
|
+
suffix += 1;
|
|
6382
|
+
}
|
|
6383
|
+
used.add(name);
|
|
6384
|
+
return name;
|
|
6385
|
+
}
|
|
6386
|
+
/**
|
|
6387
|
+
* A rule's `description` is the whole instruction Augment acts on, so the check
|
|
6388
|
+
* body is preferred; the frontmatter `description` is a summary, used only when
|
|
6389
|
+
* there is no body. Both empty falls back to the check name, which at least
|
|
6390
|
+
* names the concern rather than emitting an empty required field.
|
|
6391
|
+
*/
|
|
6392
|
+
function toRuleDescription(rulesyncCheck) {
|
|
6393
|
+
const body = rulesyncCheck.getBody().trim();
|
|
6394
|
+
if (body.length > 0) return body;
|
|
6395
|
+
return rulesyncCheck.getFrontmatter().description?.trim() || stemOf$1(rulesyncCheck);
|
|
6396
|
+
}
|
|
6397
|
+
function toRule(rulesyncCheck, override, usedRuleIds) {
|
|
6398
|
+
const severity = rulesyncCheck.getFrontmatter().severity;
|
|
6399
|
+
return {
|
|
6400
|
+
id: uniqueName(override.id ?? slugifyCheckName(stemOf$1(rulesyncCheck)), usedRuleIds),
|
|
6401
|
+
description: toRuleDescription(rulesyncCheck),
|
|
6402
|
+
severity: severity ? CANONICAL_TO_AUGMENTCODE_SEVERITY[severity] ?? DEFAULT_AUGMENTCODE_SEVERITY : DEFAULT_AUGMENTCODE_SEVERITY
|
|
6403
|
+
};
|
|
6404
|
+
}
|
|
6405
|
+
/**
|
|
6406
|
+
* An authored area key is used verbatim. Slugifying it would rewrite the
|
|
6407
|
+
* underscores in Augment's own documented example (`memory_safety`), and since
|
|
6408
|
+
* import writes the key back unchanged, the next generate would build a second
|
|
6409
|
+
* area under the slugified spelling while the original stayed put — the same
|
|
6410
|
+
* rules twice. Only the file-stem default is slugified, because that one has to
|
|
6411
|
+
* become a legal area key from an arbitrary file name.
|
|
6412
|
+
*/
|
|
6413
|
+
function areaKeyFor(rulesyncCheck, override) {
|
|
6414
|
+
return override.area ?? slugifyCheckName(stemOf$1(rulesyncCheck));
|
|
6415
|
+
}
|
|
6416
|
+
/** Rule ids belonging to areas this generate preserves rather than rewrites. */
|
|
6417
|
+
function collectRuleIds(existingAreas, claimedKeys) {
|
|
6418
|
+
const ids = /* @__PURE__ */ new Set();
|
|
6419
|
+
for (const [areaKey, rawArea] of Object.entries(existingAreas)) {
|
|
6420
|
+
if (claimedKeys.has(areaKey) || !isPlainObject$1(rawArea)) continue;
|
|
6421
|
+
for (const rawRule of readArea(rawArea).rules) {
|
|
6422
|
+
const rule = readRule(rawRule);
|
|
6423
|
+
if (rule) ids.add(rule.id);
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
return ids;
|
|
6427
|
+
}
|
|
6428
|
+
/**
|
|
6429
|
+
* Group the checks into `areas`. One area per check by default; checks naming
|
|
6430
|
+
* the same `augmentcode.area` share one, with the first check to name it
|
|
6431
|
+
* supplying the area's `description` and `globs`.
|
|
6432
|
+
*/
|
|
6433
|
+
function buildAreas({ entries, reservedRuleIds }) {
|
|
6434
|
+
const areas = /* @__PURE__ */ new Map();
|
|
6435
|
+
const usedRuleIds = new Set(reservedRuleIds);
|
|
6436
|
+
for (const { rulesyncCheck, override } of entries) {
|
|
6437
|
+
const key = areaKeyFor(rulesyncCheck, override);
|
|
6438
|
+
const rule = toRule(rulesyncCheck, override, usedRuleIds);
|
|
6439
|
+
const existing = areas.get(key);
|
|
6440
|
+
if (existing) {
|
|
6441
|
+
existing.rules.push(rule);
|
|
6442
|
+
continue;
|
|
6443
|
+
}
|
|
6444
|
+
areas.set(key, {
|
|
6445
|
+
description: override.areaDescription || rulesyncCheck.getFrontmatter().description?.trim() || stemOf$1(rulesyncCheck),
|
|
6446
|
+
globs: override.globs ?? defaultAreaGlobs(),
|
|
6447
|
+
rules: [rule]
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
return Object.fromEntries(areas);
|
|
6451
|
+
}
|
|
6452
|
+
function parseGuidelines(fileContent, filePath) {
|
|
6453
|
+
if (fileContent.trim().length === 0) return {};
|
|
6454
|
+
let parsed;
|
|
6455
|
+
try {
|
|
6456
|
+
parsed = loadYaml(fileContent);
|
|
6457
|
+
} catch (error) {
|
|
6458
|
+
throw new Error(`Failed to parse AugmentCode code review guidelines at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
6459
|
+
}
|
|
6460
|
+
if (parsed === void 0 || parsed === null) return {};
|
|
6461
|
+
if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse AugmentCode code review guidelines at ${filePath}: expected a mapping at the document root.`);
|
|
6462
|
+
return parsed;
|
|
6463
|
+
}
|
|
6464
|
+
/** An area's shared fields, read defensively out of hand-written YAML. */
|
|
6465
|
+
function readArea(rawArea) {
|
|
6466
|
+
const rawGlobs = Array.isArray(rawArea.globs) ? rawArea.globs : void 0;
|
|
6467
|
+
const globs = rawGlobs?.filter((glob) => typeof glob === "string");
|
|
6468
|
+
const authoredGlobs = globs && (globs.length > 0 || rawGlobs?.length === 0) ? globs : void 0;
|
|
6469
|
+
return {
|
|
6470
|
+
...typeof rawArea.description === "string" && { description: rawArea.description },
|
|
6471
|
+
...authoredGlobs && { globs: authoredGlobs },
|
|
6472
|
+
rules: Array.isArray(rawArea.rules) ? rawArea.rules : []
|
|
6473
|
+
};
|
|
6474
|
+
}
|
|
6475
|
+
/**
|
|
6476
|
+
* A rule missing `id` or `description` is not a shape Augment itself reads, so
|
|
6477
|
+
* it is skipped rather than imported as a check with an invented field.
|
|
6478
|
+
*/
|
|
6479
|
+
function readRule(rawRule) {
|
|
6480
|
+
if (!isPlainObject$1(rawRule)) return void 0;
|
|
6481
|
+
const id = typeof rawRule.id === "string" ? rawRule.id : void 0;
|
|
6482
|
+
const description = typeof rawRule.description === "string" ? rawRule.description : void 0;
|
|
6483
|
+
if (!id || !description) return void 0;
|
|
6484
|
+
const severity = AUGMENTCODE_SEVERITIES.find((value) => value === rawRule.severity);
|
|
6485
|
+
return {
|
|
6486
|
+
id,
|
|
6487
|
+
description,
|
|
6488
|
+
...severity && { severity }
|
|
6489
|
+
};
|
|
6490
|
+
}
|
|
6491
|
+
/**
|
|
6492
|
+
* Checks adapter for Augment Code Review's custom guidelines
|
|
6493
|
+
* (`.augment/code_review_guidelines.yaml`).
|
|
6494
|
+
*
|
|
6495
|
+
* Augment groups review rules into named **areas**: each area carries a
|
|
6496
|
+
* `description`, the `globs` it applies to, and a list of `rules`, every rule
|
|
6497
|
+
* being an `id` / `description` / `severity` triple. Every one of those fields
|
|
6498
|
+
* is required upstream. Rulesync maps one `.rulesync/checks/*.md` onto one rule,
|
|
6499
|
+
* in an area of its own unless an `augmentcode.area` groups several together —
|
|
6500
|
+
* so the whole set collapses into this one file via {@link fromRulesyncChecks}
|
|
6501
|
+
* rather than a file per check.
|
|
6502
|
+
*
|
|
6503
|
+
* **Severity is lossy in one direction.** Augment's scale is `high` / `medium` /
|
|
6504
|
+
* `low` with no band above `high`, so canonical `critical` maps to `high` and
|
|
6505
|
+
* imports back as `high`. A check with no `severity` at all emits `medium`,
|
|
6506
|
+
* since the field cannot be omitted.
|
|
6507
|
+
*
|
|
6508
|
+
* **`file_paths_to_ignore`** is recognized and preserved verbatim, but never
|
|
6509
|
+
* authored or imported: the canonical check model has no ignore surface, and
|
|
6510
|
+
* inventing one here is a separate design question.
|
|
6511
|
+
*
|
|
6512
|
+
* **Generation merges rather than replaces.** Augment's own documentation tells
|
|
6513
|
+
* users to hand-write this file, so only the areas the current check set claims
|
|
6514
|
+
* are rewritten; every other area, `file_paths_to_ignore`, and any unknown
|
|
6515
|
+
* top-level key survive. The cost of that is that rulesync cannot tell its own
|
|
6516
|
+
* leftovers from a hand-written area: renaming a check strands the area under
|
|
6517
|
+
* the old key, and dropping the last Augment-targeting check leaves the areas in
|
|
6518
|
+
* place with a warning rather than guessing which of them to delete. Deleting
|
|
6519
|
+
* the file outright is likewise refused whenever it exists.
|
|
6520
|
+
*
|
|
6521
|
+
* Project scope only — the reviewer reads the file from the committed
|
|
6522
|
+
* repository, and Augment documents no user-level equivalent.
|
|
6523
|
+
*
|
|
6524
|
+
* @see https://docs.augmentcode.com/codereview/review-guidelines
|
|
6525
|
+
*/
|
|
6526
|
+
var AugmentcodeCheck = class AugmentcodeCheck extends ToolCheck {
|
|
6527
|
+
static getSettablePaths(_options = {}) {
|
|
6528
|
+
return {
|
|
6529
|
+
relativeDirPath: AUGMENTCODE_DIR,
|
|
6530
|
+
relativeFilePath: AUGMENTCODE_CODE_REVIEW_GUIDELINES_FILE_NAME
|
|
6531
|
+
};
|
|
6532
|
+
}
|
|
6533
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6534
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6535
|
+
rulesyncCheck,
|
|
6536
|
+
toolTarget: "augmentcode"
|
|
6537
|
+
});
|
|
6538
|
+
}
|
|
6539
|
+
/**
|
|
6540
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6541
|
+
* tool. Unlike the Markdown adapters, whose section markers say which text is
|
|
6542
|
+
* rulesync's, YAML carries no such marker — js-yaml drops comments on rewrite,
|
|
6543
|
+
* and an unknown top-level key risks Augment's own parser. So an existing file
|
|
6544
|
+
* is never rulesync's to delete: it may hold hand-written areas, and there is
|
|
6545
|
+
* no way to prove otherwise.
|
|
6546
|
+
*/
|
|
6547
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6548
|
+
const paths = AugmentcodeCheck.getSettablePaths();
|
|
6549
|
+
return !await fileExists(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "code_review_guidelines.yaml"));
|
|
6550
|
+
}
|
|
6551
|
+
static fromRulesyncCheck(_params) {
|
|
6552
|
+
throw new Error("AugmentCode checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6553
|
+
}
|
|
6554
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6555
|
+
const paths = AugmentcodeCheck.getSettablePaths({ global });
|
|
6556
|
+
const relativeFilePath = paths.relativeFilePath ?? "code_review_guidelines.yaml";
|
|
6557
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6558
|
+
const existing = parseGuidelines(await readFileContentOrNull(filePath) ?? "", filePath);
|
|
6559
|
+
const existingAreas = isPlainObject$1(existing.areas) ? existing.areas : {};
|
|
6560
|
+
if (rulesyncChecks.length === 0) {
|
|
6561
|
+
if (Object.keys(existingAreas).length > 0) logger?.warn(`AugmentCode checks: no check targets AugmentCode, but ${filePath} still holds review areas. They are left in place — rulesync cannot tell the ones it generated from ones you wrote, so removing them is a manual edit.`);
|
|
6562
|
+
return [];
|
|
6563
|
+
}
|
|
6564
|
+
const entries = rulesyncChecks.map((rulesyncCheck) => ({
|
|
6565
|
+
rulesyncCheck,
|
|
6566
|
+
override: parseOverride$1(rulesyncCheck.getFrontmatter().augmentcode, join(RULESYNC_CHECKS_RELATIVE_DIR_PATH, rulesyncCheck.getRelativeFilePath()), logger)
|
|
6567
|
+
}));
|
|
6568
|
+
const generatedAreas = buildAreas({
|
|
6569
|
+
entries,
|
|
6570
|
+
reservedRuleIds: collectRuleIds(existingAreas, new Set(entries.map(({ rulesyncCheck, override }) => areaKeyFor(rulesyncCheck, override))))
|
|
6571
|
+
});
|
|
6572
|
+
const fileContent = dump({
|
|
6573
|
+
...existing,
|
|
6574
|
+
areas: {
|
|
6575
|
+
...existingAreas,
|
|
6576
|
+
...generatedAreas
|
|
6577
|
+
}
|
|
6578
|
+
}, {
|
|
6579
|
+
lineWidth: -1,
|
|
6580
|
+
noRefs: true
|
|
6581
|
+
});
|
|
6582
|
+
return [new AugmentcodeCheck({
|
|
6583
|
+
outputRoot,
|
|
6584
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6585
|
+
relativeFilePath,
|
|
6586
|
+
fileContent,
|
|
6587
|
+
global
|
|
6588
|
+
})];
|
|
6589
|
+
}
|
|
6590
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6591
|
+
const paths = AugmentcodeCheck.getSettablePaths({ global });
|
|
6592
|
+
const relativeFilePath = paths.relativeFilePath ?? "code_review_guidelines.yaml";
|
|
6593
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6594
|
+
return new AugmentcodeCheck({
|
|
6595
|
+
outputRoot,
|
|
6596
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6597
|
+
relativeFilePath,
|
|
6598
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6599
|
+
global
|
|
6600
|
+
});
|
|
6601
|
+
}
|
|
6602
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6603
|
+
return new AugmentcodeCheck({
|
|
6604
|
+
outputRoot,
|
|
6605
|
+
relativeDirPath,
|
|
6606
|
+
relativeFilePath,
|
|
6607
|
+
fileContent: "",
|
|
6608
|
+
validate: false,
|
|
6609
|
+
global
|
|
6610
|
+
});
|
|
6611
|
+
}
|
|
6612
|
+
validate() {
|
|
6613
|
+
return {
|
|
6614
|
+
success: true,
|
|
6615
|
+
error: null
|
|
6616
|
+
};
|
|
6617
|
+
}
|
|
6618
|
+
toRulesyncCheck() {
|
|
6619
|
+
const first = this.toRulesyncChecks()[0];
|
|
6620
|
+
if (!first) throw new Error(`No review areas found in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6621
|
+
return first;
|
|
6622
|
+
}
|
|
6623
|
+
/**
|
|
6624
|
+
* One check per rule, not per area: an area with three rules is three
|
|
6625
|
+
* independent review instructions, and collapsing them into one check would
|
|
6626
|
+
* make the next generate emit a single merged rule in their place. The area's
|
|
6627
|
+
* key, description and globs ride along in each check's `augmentcode` block,
|
|
6628
|
+
* so regenerating regroups the rules exactly where they were.
|
|
6629
|
+
*/
|
|
6630
|
+
toRulesyncChecks() {
|
|
6631
|
+
const filePath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
6632
|
+
const guidelines = parseGuidelines(this.getFileContent(), filePath);
|
|
6633
|
+
const areas = isPlainObject$1(guidelines.areas) ? guidelines.areas : {};
|
|
6634
|
+
const checks = [];
|
|
6635
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
6636
|
+
for (const [areaKey, rawArea] of Object.entries(areas)) {
|
|
6637
|
+
if (!isPlainObject$1(rawArea)) continue;
|
|
6638
|
+
const { description: areaDescription, globs, rules } = readArea(rawArea);
|
|
6639
|
+
for (const rawRule of rules) {
|
|
6640
|
+
const rule = readRule(rawRule);
|
|
6641
|
+
if (!rule) continue;
|
|
6642
|
+
const name = uniqueName(slugifyCheckName(rule.id) || slugifyCheckName(areaKey) || "check", usedNames);
|
|
6643
|
+
checks.push(new RulesyncCheck({
|
|
6644
|
+
outputRoot: ".",
|
|
6645
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
6646
|
+
relativeFilePath: `${name}.md`,
|
|
6647
|
+
frontmatter: {
|
|
6648
|
+
targets: ["*"],
|
|
6649
|
+
...rule.severity && { severity: rule.severity },
|
|
6650
|
+
augmentcode: {
|
|
6651
|
+
area: areaKey,
|
|
6652
|
+
...areaDescription !== void 0 && { areaDescription },
|
|
6653
|
+
...globs && { globs },
|
|
6654
|
+
id: rule.id
|
|
6655
|
+
}
|
|
6656
|
+
},
|
|
6657
|
+
body: rule.description
|
|
6658
|
+
}));
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
return checks;
|
|
6662
|
+
}
|
|
6663
|
+
};
|
|
6664
|
+
//#endregion
|
|
6162
6665
|
//#region src/features/checks/aggregated-check-file.ts
|
|
6163
6666
|
/**
|
|
6164
6667
|
* Shared machinery for the tools whose checks surface is **one aggregated
|
|
@@ -7081,6 +7584,22 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7081
7584
|
}
|
|
7082
7585
|
}
|
|
7083
7586
|
},
|
|
7587
|
+
".github/copilot/settings.json": {
|
|
7588
|
+
format: "json",
|
|
7589
|
+
invalidRootPolicy: "error",
|
|
7590
|
+
features: { permissions: {
|
|
7591
|
+
kind: "replace-owned-keys",
|
|
7592
|
+
ownedKeys: ["deniedUrls"]
|
|
7593
|
+
} }
|
|
7594
|
+
},
|
|
7595
|
+
".copilot/settings.json": {
|
|
7596
|
+
format: "json",
|
|
7597
|
+
invalidRootPolicy: "error",
|
|
7598
|
+
features: { permissions: {
|
|
7599
|
+
kind: "replace-owned-keys",
|
|
7600
|
+
ownedKeys: ["allowedUrls", "deniedUrls"]
|
|
7601
|
+
} }
|
|
7602
|
+
},
|
|
7084
7603
|
".vscode/settings.json": {
|
|
7085
7604
|
format: "jsonc",
|
|
7086
7605
|
invalidRootPolicy: "error",
|
|
@@ -7161,6 +7680,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7161
7680
|
}
|
|
7162
7681
|
}
|
|
7163
7682
|
},
|
|
7683
|
+
".config/muse/settings.json": {
|
|
7684
|
+
format: "json",
|
|
7685
|
+
invalidRootPolicy: "error",
|
|
7686
|
+
features: { mcp: {
|
|
7687
|
+
kind: "replace-owned-keys",
|
|
7688
|
+
ownedKeys: ["mcp_servers", "schema_version"]
|
|
7689
|
+
} }
|
|
7690
|
+
},
|
|
7164
7691
|
".kiro/agents/default.json": {
|
|
7165
7692
|
format: "json",
|
|
7166
7693
|
features: {
|
|
@@ -7808,6 +8335,14 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7808
8335
|
filePattern: "*.md"
|
|
7809
8336
|
}
|
|
7810
8337
|
}],
|
|
8338
|
+
["augmentcode", {
|
|
8339
|
+
class: AugmentcodeCheck,
|
|
8340
|
+
meta: {
|
|
8341
|
+
supportsGlobal: false,
|
|
8342
|
+
filePattern: AUGMENTCODE_CODE_REVIEW_GUIDELINES_FILE_NAME,
|
|
8343
|
+
committedOutput: true
|
|
8344
|
+
}
|
|
8345
|
+
}],
|
|
7811
8346
|
["cursor", {
|
|
7812
8347
|
class: CursorCheck,
|
|
7813
8348
|
meta: {
|
|
@@ -8595,26 +9130,6 @@ var AntigravityIdeCommand = class extends AntigravitySharedCommand {
|
|
|
8595
9130
|
}
|
|
8596
9131
|
};
|
|
8597
9132
|
//#endregion
|
|
8598
|
-
//#region src/constants/augmentcode-paths.ts
|
|
8599
|
-
const AUGMENTCODE_DIR = ".augment";
|
|
8600
|
-
const AUGMENTCODE_COMMANDS_DIR_PATH = join(AUGMENTCODE_DIR, "commands");
|
|
8601
|
-
/**
|
|
8602
|
-
* The cross-tool `.agents/commands/` root Auggie also discovers commands from.
|
|
8603
|
-
* Auggie resolves commands over `.augment`, `.claude` and `.agents` in both the
|
|
8604
|
-
* workspace and the home directory; rulesync writes to its own `.augment` root
|
|
8605
|
-
* and reads this one as well, mirroring how skills treat `.agents/skills`.
|
|
8606
|
-
* @see https://docs.augmentcode.com/cli/custom-commands
|
|
8607
|
-
*/
|
|
8608
|
-
const AUGMENTCODE_AGENTS_COMMANDS_DIR_PATH = join(".agents", "commands");
|
|
8609
|
-
const AUGMENTCODE_SKILLS_DIR_PATH = join(AUGMENTCODE_DIR, "skills");
|
|
8610
|
-
const AUGMENTCODE_AGENTS_DIR_PATH = join(AUGMENTCODE_DIR, "agents");
|
|
8611
|
-
const AUGMENTCODE_ALT_AGENTS_DIR_PATH = ".agents";
|
|
8612
|
-
const AUGMENTCODE_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
8613
|
-
const AUGMENTCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
8614
|
-
const AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME = "settings.local.json";
|
|
8615
|
-
const AUGMENTCODE_IGNORE_FILE_NAME = ".augmentignore";
|
|
8616
|
-
const AUGMENTCODE_LEGACY_RULE_FILE_NAME = ".augment-guidelines";
|
|
8617
|
-
//#endregion
|
|
8618
9133
|
//#region src/features/commands/augmentcode-command.ts
|
|
8619
9134
|
const AugmentcodeCommandFrontmatterSchema = z.looseObject({
|
|
8620
9135
|
description: z.optional(z.string()),
|
|
@@ -9148,6 +9663,8 @@ const COPILOT_GLOBAL_HOOKS_FILE_NAME = "copilot-ide-hooks.json";
|
|
|
9148
9663
|
const COPILOT_MCP_DIR = ".vscode";
|
|
9149
9664
|
const COPILOT_MCP_FILE_NAME = "mcp.json";
|
|
9150
9665
|
const COPILOT_VSCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
9666
|
+
const COPILOTCLI_SETTINGS_FILE_NAME = "settings.json";
|
|
9667
|
+
const COPILOTCLI_PROJECT_SETTINGS_DIR_PATH = join(GITHUB_DIR, "copilot");
|
|
9151
9668
|
const COPILOTCLI_MCP_FILE_NAME = "mcp-config.json";
|
|
9152
9669
|
const COPILOTCLI_PROJECT_MCP_FILE_NAME = "mcp.json";
|
|
9153
9670
|
const COPILOTCLI_AGENTS_DIR_PATH = join(COPILOT_DIR, "agents");
|
|
@@ -9407,6 +9924,7 @@ const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
|
9407
9924
|
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
9408
9925
|
const DEVIN_IGNORE_FILE_NAME = ".devinignore";
|
|
9409
9926
|
const DEVIN_LEGACY_IGNORE_FILE_NAME = ".codeiumignore";
|
|
9927
|
+
const DEVIN_WINDSURF_IGNORE_FILE_NAME = ".windsurfignore";
|
|
9410
9928
|
const DEVIN_GLOBAL_IGNORE_DIR_PATH = ".codeium";
|
|
9411
9929
|
const DEVIN_GLOBAL_IGNORE_FILE_NAME = DEVIN_LEGACY_IGNORE_FILE_NAME;
|
|
9412
9930
|
//#endregion
|
|
@@ -9761,7 +10279,7 @@ var GooseCommandConfigFile = class extends ToolFile {
|
|
|
9761
10279
|
* The whole file is a YAML mapping (not frontmatter + markdown body), so the
|
|
9762
10280
|
* class stores the parsed recipe object rather than a frontmatter/body split.
|
|
9763
10281
|
*
|
|
9764
|
-
* @see https://
|
|
10282
|
+
* @see https://goose-docs.ai/docs/guides/recipes/recipe-reference/
|
|
9765
10283
|
*/
|
|
9766
10284
|
const GooseCommandRecipeSchema = z.looseObject({
|
|
9767
10285
|
version: z.optional(z.string()),
|
|
@@ -13255,7 +13773,7 @@ function collectHandlers({ effectiveHooks, eventMap }) {
|
|
|
13255
13773
|
}
|
|
13256
13774
|
return handlerGroups;
|
|
13257
13775
|
}
|
|
13258
|
-
function buildCommandLines({ handler, usesToolName, blocksToolCall }) {
|
|
13776
|
+
function buildCommandLines$1({ handler, usesToolName, blocksToolCall }) {
|
|
13259
13777
|
const lines = [];
|
|
13260
13778
|
const indent = usesToolName && handler.matcher ? " " : " ";
|
|
13261
13779
|
if (usesToolName && handler.matcher) lines.push(` if (new RegExp(${matcherToEmbeddedLiteral$1(handler.matcher)}).test(event.tool)) {`);
|
|
@@ -13280,7 +13798,7 @@ function buildSubscriptionLines$1(handlerGroups) {
|
|
|
13280
13798
|
const usesToolName = AMP_TOOL_EVENTS.has(ampEvent) && handlers.some((handler) => handler.matcher);
|
|
13281
13799
|
const blocksToolCall = ampEvent === "tool.call";
|
|
13282
13800
|
lines.push(` amp.on(${JSON.stringify(ampEvent)}, async (${usesToolName ? "event, ctx" : "_event, ctx"}) => {`);
|
|
13283
|
-
for (const handler of handlers) lines.push(...buildCommandLines({
|
|
13801
|
+
for (const handler of handlers) lines.push(...buildCommandLines$1({
|
|
13284
13802
|
handler,
|
|
13285
13803
|
usesToolName,
|
|
13286
13804
|
blocksToolCall
|
|
@@ -15265,8 +15783,10 @@ const CopilotHookEntrySchema = z.looseObject({
|
|
|
15265
15783
|
bash: z.optional(z.string()),
|
|
15266
15784
|
powershell: z.optional(z.string()),
|
|
15267
15785
|
command: z.optional(z.string()),
|
|
15786
|
+
cwd: z.optional(z.string()),
|
|
15268
15787
|
env: z.optional(z.record(z.string(), z.string())),
|
|
15269
|
-
timeoutSec: z.optional(z.number())
|
|
15788
|
+
timeoutSec: z.optional(z.number()),
|
|
15789
|
+
timeout: z.optional(z.number())
|
|
15270
15790
|
});
|
|
15271
15791
|
/**
|
|
15272
15792
|
* Convert canonical hooks config to Copilot format.
|
|
@@ -15346,6 +15866,22 @@ function resolveImportCommand$1(entry, logger) {
|
|
|
15346
15866
|
return typeof entry.command === "string" ? { command: entry.command } : {};
|
|
15347
15867
|
}
|
|
15348
15868
|
/**
|
|
15869
|
+
* Extract the non-command fields preserved across import.
|
|
15870
|
+
*
|
|
15871
|
+
* Generate re-emits any non-canonical key verbatim through `rest`, so a key
|
|
15872
|
+
* dropped here does not survive an import → generate round trip. `cwd` is a
|
|
15873
|
+
* documented Copilot hook field and was previously lost that way.
|
|
15874
|
+
*
|
|
15875
|
+
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
15876
|
+
* @see https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks
|
|
15877
|
+
*/
|
|
15878
|
+
function importPassthrough$1(entry) {
|
|
15879
|
+
const passthrough = {};
|
|
15880
|
+
if (entry.cwd !== void 0) passthrough.cwd = entry.cwd;
|
|
15881
|
+
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
15882
|
+
return passthrough;
|
|
15883
|
+
}
|
|
15884
|
+
/**
|
|
15349
15885
|
* Extract hooks from Copilot hooks JSON into canonical format.
|
|
15350
15886
|
* Copilot format: { version: 1, hooks: { eventName: [...hookEntries] } }
|
|
15351
15887
|
*/
|
|
@@ -15361,13 +15897,13 @@ function copilotHooksToCanonical(copilotHooks, logger) {
|
|
|
15361
15897
|
if (!parseResult.success) continue;
|
|
15362
15898
|
const entry = parseResult.data;
|
|
15363
15899
|
const { command, shell } = resolveImportCommand$1(entry, logger);
|
|
15364
|
-
const timeout = entry.timeoutSec;
|
|
15900
|
+
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
15365
15901
|
defs.push({
|
|
15366
15902
|
type: "command",
|
|
15367
15903
|
...command !== void 0 && { command },
|
|
15368
15904
|
...shell !== void 0 && { shell },
|
|
15369
|
-
...
|
|
15370
|
-
...
|
|
15905
|
+
...timeout !== void 0 && { timeout },
|
|
15906
|
+
...importPassthrough$1(entry)
|
|
15371
15907
|
});
|
|
15372
15908
|
}
|
|
15373
15909
|
if (defs.length > 0) canonical[eventName] = defs;
|
|
@@ -15650,19 +16186,19 @@ function importPassthrough(entry) {
|
|
|
15650
16186
|
* A shell-specific field carries its `shell` through so re-export writes the
|
|
15651
16187
|
* same field back. An entry using only the portable `command` field leaves
|
|
15652
16188
|
* `shell` unset, which re-export renders as the portable field again.
|
|
16189
|
+
*
|
|
16190
|
+
* When both shell-specific fields are present, `bash` wins and a warning names
|
|
16191
|
+
* the ignored `powershell`. The choice is deliberately not platform-dependent:
|
|
16192
|
+
* importing on Windows must not produce a different canonical config than
|
|
16193
|
+
* importing the same file on Linux, which would make the rulesync hooks file
|
|
16194
|
+
* differ per machine for anyone who checks it in.
|
|
15653
16195
|
*/
|
|
15654
16196
|
function resolveImportCommand(entry, logger) {
|
|
15655
16197
|
const hasBash = typeof entry.bash === "string";
|
|
15656
16198
|
const hasPowershell = typeof entry.powershell === "string";
|
|
15657
16199
|
if (hasBash && hasPowershell) {
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
const ignored = isWindows ? "bash" : "powershell";
|
|
15661
|
-
logger?.warn(`Copilot CLI hook has both bash and powershell commands; using ${chosen} and ignoring ${ignored} on this platform.`);
|
|
15662
|
-
return isWindows ? {
|
|
15663
|
-
command: entry.powershell,
|
|
15664
|
-
shell: "powershell"
|
|
15665
|
-
} : {
|
|
16200
|
+
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.");
|
|
16201
|
+
return {
|
|
15666
16202
|
command: entry.bash,
|
|
15667
16203
|
shell: "bash"
|
|
15668
16204
|
};
|
|
@@ -16321,7 +16857,7 @@ const GOOSE_CONVERTER_CONFIG = {
|
|
|
16321
16857
|
*
|
|
16322
16858
|
* The JSON shape matches Claude Code's: each PascalCase event maps to an array of
|
|
16323
16859
|
* `{ matcher, hooks: [{ type: "command", command }] }` entries.
|
|
16324
|
-
* @see https://
|
|
16860
|
+
* @see https://goose-docs.ai/docs/guides/context-engineering/hooks/
|
|
16325
16861
|
*/
|
|
16326
16862
|
var GooseHooks = class GooseHooks extends ToolHooks {
|
|
16327
16863
|
constructor(params) {
|
|
@@ -16522,6 +17058,13 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
|
16522
17058
|
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
16523
17059
|
*/
|
|
16524
17060
|
const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["pre_tool_call", "post_tool_call"]);
|
|
17061
|
+
/**
|
|
17062
|
+
* The only Hermes event that can block, and therefore the only one whose
|
|
17063
|
+
* entries accept `fail_closed`. Upstream warns and ignores the key on every
|
|
17064
|
+
* other event ("only blocking-capable events can fail closed").
|
|
17065
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
17066
|
+
*/
|
|
17067
|
+
const HERMESAGENT_FAIL_CLOSED_EVENT = "pre_tool_call";
|
|
16525
17068
|
const HERMESAGENT_CANONICAL_EVENTS = new Set(HERMESAGENT_HOOK_EVENTS);
|
|
16526
17069
|
const HERMESAGENT_NATIVE_EVENTS = new Set(HERMESAGENT_NATIVE_HOOK_EVENTS);
|
|
16527
17070
|
/**
|
|
@@ -16555,7 +17098,9 @@ function isHermesHookEventEntry(key, value) {
|
|
|
16555
17098
|
* unsupported hook types centrally). `matcher` is only carried through for
|
|
16556
17099
|
* `pre_tool_call`/`post_tool_call`; on any other event it is dropped with a
|
|
16557
17100
|
* warning, mirroring how other adapters (e.g. AugmentCode) handle
|
|
16558
|
-
* matcher-less lifecycle events.
|
|
17101
|
+
* matcher-less lifecycle events. The canonical `failClosed` field (shared with
|
|
17102
|
+
* Cursor, whose semantics Hermes copied) becomes `fail_closed`, which upstream
|
|
17103
|
+
* only honours on `pre_tool_call`.
|
|
16559
17104
|
*/
|
|
16560
17105
|
function definitionsToHermesEntries({ event, sourceEvent = event, definitions, logger }) {
|
|
16561
17106
|
const supportsMatcher = HERMESAGENT_MATCHER_EVENTS.has(event);
|
|
@@ -16566,6 +17111,10 @@ function definitionsToHermesEntries({ event, sourceEvent = event, definitions, l
|
|
|
16566
17111
|
if (typeof definition.matcher === "string" && definition.matcher !== "") if (supportsMatcher) entry.matcher = definition.matcher;
|
|
16567
17112
|
else logger?.warn(`matcher "${definition.matcher}" on "${sourceEvent}" hook will be ignored — Hermes Agent only supports matchers on pre_tool_call/post_tool_call`);
|
|
16568
17113
|
if (typeof definition.timeout === "number") entry.timeout = definition.timeout;
|
|
17114
|
+
if (typeof definition.failClosed === "boolean") {
|
|
17115
|
+
if (event === HERMESAGENT_FAIL_CLOSED_EVENT) entry.fail_closed = definition.failClosed;
|
|
17116
|
+
else if (definition.failClosed) logger?.warn(`failClosed on "${sourceEvent}" hook will be ignored — Hermes Agent only supports fail_closed on pre_tool_call`);
|
|
17117
|
+
}
|
|
16569
17118
|
entries.push(entry);
|
|
16570
17119
|
}
|
|
16571
17120
|
return entries;
|
|
@@ -16617,6 +17166,37 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
|
16617
17166
|
return result;
|
|
16618
17167
|
}
|
|
16619
17168
|
/**
|
|
17169
|
+
* Reads a hook entry's fail-closed flag. Upstream accepts its own `fail_closed`
|
|
17170
|
+
* and the Cursor/Claude Code `failClosed` spelling alike, so both have to be
|
|
17171
|
+
* read back into the single canonical field.
|
|
17172
|
+
*/
|
|
17173
|
+
function readHermesFailClosed(entry) {
|
|
17174
|
+
if (typeof entry.fail_closed === "boolean") return entry.fail_closed;
|
|
17175
|
+
if (typeof entry.failClosed === "boolean") return entry.failClosed;
|
|
17176
|
+
}
|
|
17177
|
+
/**
|
|
17178
|
+
* Converts one serialized Hermes hook entry back into a canonical definition,
|
|
17179
|
+
* or `undefined` when it is not a hook rulesync models (no string `command`).
|
|
17180
|
+
* `matcher` and `fail_closed` are read only on the events upstream honours them
|
|
17181
|
+
* on, so an imported value is never one the next generate warns about and drops.
|
|
17182
|
+
*/
|
|
17183
|
+
function hermesEntryToDefinition({ nativeEvent, raw }) {
|
|
17184
|
+
if (!isRecord$1(raw)) return;
|
|
17185
|
+
const entry = raw;
|
|
17186
|
+
if (typeof entry.command !== "string") return;
|
|
17187
|
+
const def = {
|
|
17188
|
+
type: "command",
|
|
17189
|
+
command: entry.command
|
|
17190
|
+
};
|
|
17191
|
+
if (HERMESAGENT_MATCHER_EVENTS.has(nativeEvent) && typeof entry.matcher === "string" && entry.matcher !== "") def.matcher = entry.matcher;
|
|
17192
|
+
if (typeof entry.timeout === "number") def.timeout = entry.timeout;
|
|
17193
|
+
if (nativeEvent === HERMESAGENT_FAIL_CLOSED_EVENT) {
|
|
17194
|
+
const failClosed = readHermesFailClosed(entry);
|
|
17195
|
+
if (failClosed !== void 0) def.failClosed = failClosed;
|
|
17196
|
+
}
|
|
17197
|
+
return def;
|
|
17198
|
+
}
|
|
17199
|
+
/**
|
|
16620
17200
|
* Reverse {@link canonicalToHermesHooks}: parse Hermes's native
|
|
16621
17201
|
* `hooks: { <event>: [...] }` map back into a canonical event → definition[]
|
|
16622
17202
|
* record. Native events with no canonical equivalent (`pre_verify`,
|
|
@@ -16630,19 +17210,10 @@ function hermesHooksToCanonical(hooks) {
|
|
|
16630
17210
|
if (PROTOTYPE_POLLUTION_KEYS.has(nativeEvent) || !Array.isArray(entries)) continue;
|
|
16631
17211
|
if (!isHermesHookEventEntry(nativeEvent, entries)) continue;
|
|
16632
17212
|
const rulesyncEvent = HERMESAGENT_TO_CANONICAL_EVENT_NAMES[nativeEvent] ?? nativeEvent;
|
|
16633
|
-
const defs =
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
if (typeof entry.command !== "string") continue;
|
|
16638
|
-
const def = {
|
|
16639
|
-
type: "command",
|
|
16640
|
-
command: entry.command
|
|
16641
|
-
};
|
|
16642
|
-
if (HERMESAGENT_MATCHER_EVENTS.has(nativeEvent) && typeof entry.matcher === "string" && entry.matcher !== "") def.matcher = entry.matcher;
|
|
16643
|
-
if (typeof entry.timeout === "number") def.timeout = entry.timeout;
|
|
16644
|
-
defs.push(def);
|
|
16645
|
-
}
|
|
17213
|
+
const defs = entries.map((raw) => hermesEntryToDefinition({
|
|
17214
|
+
nativeEvent,
|
|
17215
|
+
raw
|
|
17216
|
+
})).filter((def) => def !== void 0);
|
|
16646
17217
|
if (defs.length > 0) canonical[rulesyncEvent] = defs;
|
|
16647
17218
|
}
|
|
16648
17219
|
return canonical;
|
|
@@ -17230,6 +17801,38 @@ function stripTrustedDirectoryWrapper(command) {
|
|
|
17230
17801
|
if (posix?.[1]) return posix[1];
|
|
17231
17802
|
return command.match(/^set "RULESYNC_KIMI_HOOK_CWD=1" && cd \/d "(?:""|[^"])*" && ([\s\S]*)$/)?.[1] ?? command;
|
|
17232
17803
|
}
|
|
17804
|
+
/**
|
|
17805
|
+
* Native Kimi Code events whose Event Reference row lists the matcher as
|
|
17806
|
+
* "Empty string". Kimi Code documents `matcher` as "a regular expression to
|
|
17807
|
+
* filter event targets; if omitted, matches all", so on these events the
|
|
17808
|
+
* regex is tested against `""`: any non-trivial matcher simply never matches
|
|
17809
|
+
* and the hook silently never runs. Dropping the matcher is what makes the
|
|
17810
|
+
* hook fire at all, which is the authored intent — these events have no target
|
|
17811
|
+
* to filter on in the first place.
|
|
17812
|
+
*
|
|
17813
|
+
* Keyed on native names because the check runs after the canonical → native
|
|
17814
|
+
* mapping: `SessionHeartbeat` and `Interrupt` have no canonical counterpart and
|
|
17815
|
+
* are only reachable through a per-tool `kimi-code` override naming them
|
|
17816
|
+
* directly.
|
|
17817
|
+
*
|
|
17818
|
+
* Deliberately narrower than Claude Code's equivalent set: Kimi Code's
|
|
17819
|
+
* `UserPromptSubmit` matches the submitted prompt text, and `PermissionResult`
|
|
17820
|
+
* matches the tool name, so a matcher on either is meaningful and is kept.
|
|
17821
|
+
*
|
|
17822
|
+
* @see https://moonshotai.github.io/kimi-code/en/customization/hooks.html
|
|
17823
|
+
*/
|
|
17824
|
+
const KIMI_CODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
17825
|
+
"Stop",
|
|
17826
|
+
"SessionHeartbeat",
|
|
17827
|
+
"Interrupt"
|
|
17828
|
+
]);
|
|
17829
|
+
/** Resolve the `matcher` part of an emitted entry, dropping dead matchers. */
|
|
17830
|
+
function resolveMatcherPart({ matcher, nativeEvent, logger }) {
|
|
17831
|
+
if (!matcher) return {};
|
|
17832
|
+
if (!KIMI_CODE_NO_MATCHER_EVENTS.has(nativeEvent)) return { matcher };
|
|
17833
|
+
logger?.warn(`matcher "${matcher}" on "${nativeEvent}" hook will be ignored — this event does not support matchers`);
|
|
17834
|
+
return {};
|
|
17835
|
+
}
|
|
17233
17836
|
function buildEffectiveHooks(config, toolOverrideHooks) {
|
|
17234
17837
|
const supported = new Set(KIMI_CODE_HOOK_EVENTS);
|
|
17235
17838
|
const shared = {};
|
|
@@ -17259,7 +17862,11 @@ function canonicalToKimiCodeHooks({ config, toolOverrideHooks, trustedDirectory,
|
|
|
17259
17862
|
command: definition.command,
|
|
17260
17863
|
trustedDirectory
|
|
17261
17864
|
}),
|
|
17262
|
-
...
|
|
17865
|
+
...resolveMatcherPart({
|
|
17866
|
+
matcher: definition.matcher,
|
|
17867
|
+
nativeEvent,
|
|
17868
|
+
logger
|
|
17869
|
+
}),
|
|
17263
17870
|
...validTimeout && timeout !== void 0 && { timeout }
|
|
17264
17871
|
});
|
|
17265
17872
|
}
|
|
@@ -17457,17 +18064,33 @@ function buildKiroIdeEntriesForEvent(trigger, definitions) {
|
|
|
17457
18064
|
}
|
|
17458
18065
|
return entries;
|
|
17459
18066
|
}
|
|
17460
|
-
|
|
18067
|
+
/**
|
|
18068
|
+
* The single `HooksConfig` key every Kiro target reads its tool-specific
|
|
18069
|
+
* overrides from.
|
|
18070
|
+
*
|
|
18071
|
+
* `kiro-ide` and `kiro-cli` write the same `.kiro/hooks/rulesync.json` at both
|
|
18072
|
+
* scopes, so per-target override blocks would make that one file's content
|
|
18073
|
+
* depend on generation order (last writer wins). All Kiro variants therefore
|
|
18074
|
+
* share the `kiro` block — the same resolution the Kiro MCP and permissions
|
|
18075
|
+
* wiring already use for the file they share.
|
|
18076
|
+
*/
|
|
18077
|
+
const KIRO_HOOKS_OVERRIDE_KEY = "kiro";
|
|
18078
|
+
/**
|
|
18079
|
+
* Override keys a user might reach for that nothing reads, mapped to the key
|
|
18080
|
+
* that is actually read.
|
|
18081
|
+
*/
|
|
18082
|
+
const KIRO_HOOKS_IGNORED_OVERRIDE_KEYS = ["kiro-ide", "kiro-cli"];
|
|
18083
|
+
function canonicalToKiroIdeHooks(config) {
|
|
17461
18084
|
const kiroIdeSupported = new Set(KIRO_IDE_HOOK_EVENTS);
|
|
17462
18085
|
const sharedHooks = {};
|
|
17463
18086
|
for (const [event, defs] of Object.entries(config.hooks)) if (kiroIdeSupported.has(event)) sharedHooks[event] = defs;
|
|
17464
18087
|
const effectiveHooks = {
|
|
17465
18088
|
...sharedHooks,
|
|
17466
|
-
...config[
|
|
18089
|
+
...config[KIRO_HOOKS_OVERRIDE_KEY]?.hooks
|
|
17467
18090
|
};
|
|
17468
18091
|
const entries = [];
|
|
17469
18092
|
for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
|
|
17470
|
-
const trigger = CANONICAL_TO_KIRO_IDE_EVENT_NAMES[eventName] ?? eventName;
|
|
18093
|
+
const trigger = CANONICAL_TO_KIRO_IDE_EVENT_NAMES[eventName] ?? KIRO_LEGACY_TO_KIRO_IDE_TRIGGER_NAMES[eventName] ?? eventName;
|
|
17471
18094
|
entries.push(...buildKiroIdeEntriesForEvent(trigger, definitions));
|
|
17472
18095
|
}
|
|
17473
18096
|
return entries;
|
|
@@ -17512,6 +18135,11 @@ function kiroIdeHooksToCanonical(entries) {
|
|
|
17512
18135
|
* still writes the embedded `.kiro/agents/default.json` agent-config shape,
|
|
17513
18136
|
* which Kiro CLI 3.0 no longer reads.
|
|
17514
18137
|
*
|
|
18138
|
+
* Because both targets write the very same file, they resolve their
|
|
18139
|
+
* tool-specific overrides from one shared block
|
|
18140
|
+
* ({@link KIRO_HOOKS_OVERRIDE_KEY}) rather than per-target blocks, so
|
|
18141
|
+
* generating either or both targets always yields the same file.
|
|
18142
|
+
*
|
|
17515
18143
|
* @see https://kiro.dev/docs/hooks/
|
|
17516
18144
|
*/
|
|
17517
18145
|
var KiroIdeHooks = class extends ToolHooks {
|
|
@@ -17524,14 +18152,6 @@ var KiroIdeHooks = class extends ToolHooks {
|
|
|
17524
18152
|
}, null, 2)
|
|
17525
18153
|
});
|
|
17526
18154
|
}
|
|
17527
|
-
/**
|
|
17528
|
-
* The `HooksConfig` key whose `hooks` block provides tool-specific overrides
|
|
17529
|
-
* for this target. {@link import("./kiro-cli-hooks.js").KiroCliHooks}
|
|
17530
|
-
* overrides this to `kiro-cli`.
|
|
17531
|
-
*/
|
|
17532
|
-
static getOverrideKey() {
|
|
17533
|
-
return "kiro-ide";
|
|
17534
|
-
}
|
|
17535
18155
|
static getSettablePaths(_options = {}) {
|
|
17536
18156
|
return {
|
|
17537
18157
|
relativeDirPath: KIRO_IDE_HOOKS_DIR_PATH,
|
|
@@ -17552,9 +18172,14 @@ var KiroIdeHooks = class extends ToolHooks {
|
|
|
17552
18172
|
validate
|
|
17553
18173
|
});
|
|
17554
18174
|
}
|
|
17555
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
18175
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
17556
18176
|
const paths = this.getSettablePaths({ global });
|
|
17557
|
-
const
|
|
18177
|
+
const config = rulesyncHooks.getJson();
|
|
18178
|
+
for (const ignoredKey of KIRO_HOOKS_IGNORED_OVERRIDE_KEYS) {
|
|
18179
|
+
if (config[ignoredKey]?.hooks === void 0) continue;
|
|
18180
|
+
logger?.warn(`The "${ignoredKey}.hooks" block in ${join(rulesyncHooks.getRelativeDirPath(), rulesyncHooks.getRelativeFilePath())} is ignored. Author it under the "${KIRO_HOOKS_OVERRIDE_KEY}.hooks" key instead: the Kiro IDE and Kiro CLI targets write the same hooks file, so they read one shared block.`);
|
|
18181
|
+
}
|
|
18182
|
+
const hooks = canonicalToKiroIdeHooks(config);
|
|
17558
18183
|
const fileContent = JSON.stringify({
|
|
17559
18184
|
version: "v1",
|
|
17560
18185
|
hooks
|
|
@@ -17575,10 +18200,9 @@ var KiroIdeHooks = class extends ToolHooks {
|
|
|
17575
18200
|
throw new Error(`Failed to parse Kiro IDE hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
17576
18201
|
}
|
|
17577
18202
|
const hooks = kiroIdeHooksToCanonical(parsed.hooks ?? []);
|
|
17578
|
-
const overrideKey = this.constructor.getOverrideKey();
|
|
17579
18203
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
17580
18204
|
hooks,
|
|
17581
|
-
overrideKey
|
|
18205
|
+
overrideKey: KIRO_HOOKS_OVERRIDE_KEY
|
|
17582
18206
|
}), null, 2) });
|
|
17583
18207
|
}
|
|
17584
18208
|
validate() {
|
|
@@ -17606,9 +18230,13 @@ var KiroIdeHooks = class extends ToolHooks {
|
|
|
17606
18230
|
* Hooks generator for the **Kiro CLI**.
|
|
17607
18231
|
*
|
|
17608
18232
|
* Kiro CLI 3.0 reads the same standalone `.kiro/hooks/*.json` v1 format the
|
|
17609
|
-
* Kiro IDE reads
|
|
17610
|
-
*
|
|
17611
|
-
*
|
|
18233
|
+
* Kiro IDE reads — the same directory, and in rulesync the same
|
|
18234
|
+
* `.kiro/hooks/rulesync.json` file — so this is {@link KiroIdeHooks} under a
|
|
18235
|
+
* second target name, down to the shared `kiro` override block both resolve
|
|
18236
|
+
* their tool-specific hooks from. A per-target override block would make that
|
|
18237
|
+
* one file's content depend on which target was generated last, which is
|
|
18238
|
+
* exactly what the shared block avoids (the Kiro MCP and permissions wiring
|
|
18239
|
+
* resolve their shared files the same way).
|
|
17612
18240
|
*
|
|
17613
18241
|
* The embedded `.kiro/agents/default.json` agent-hook format this target used
|
|
17614
18242
|
* to emit is documented as not working in 3.0, so it is left to the deprecated
|
|
@@ -17617,11 +18245,7 @@ var KiroIdeHooks = class extends ToolHooks {
|
|
|
17617
18245
|
* @see https://kiro.dev/docs/cli/v3/hooks-migration/
|
|
17618
18246
|
* @see https://kiro.dev/docs/hooks/
|
|
17619
18247
|
*/
|
|
17620
|
-
var KiroCliHooks = class extends KiroIdeHooks {
|
|
17621
|
-
static getOverrideKey() {
|
|
17622
|
-
return "kiro-cli";
|
|
17623
|
-
}
|
|
17624
|
-
};
|
|
18248
|
+
var KiroCliHooks = class extends KiroIdeHooks {};
|
|
17625
18249
|
//#endregion
|
|
17626
18250
|
//#region src/features/hooks/kiro-hooks.ts
|
|
17627
18251
|
/**
|
|
@@ -17646,14 +18270,30 @@ function buildKiroEntriesForEvent(definitions) {
|
|
|
17646
18270
|
}
|
|
17647
18271
|
return entries;
|
|
17648
18272
|
}
|
|
17649
|
-
|
|
18273
|
+
/**
|
|
18274
|
+
* Event keys the embedded agent-config format defines: the canonical events it
|
|
18275
|
+
* supports plus its own native spellings (`agentSpawn`, `userPromptSubmit`, …).
|
|
18276
|
+
*
|
|
18277
|
+
* The `kiro` override block is shared with the standalone `.kiro/hooks/*.json`
|
|
18278
|
+
* targets, whose vocabulary is different (`PostFileSave`, `PreTaskExec`, …).
|
|
18279
|
+
* Passing those through here would write event keys Kiro does not define into
|
|
18280
|
+
* `.kiro/agents/default.json`, so they are dropped instead.
|
|
18281
|
+
* @see https://kiro.dev/docs/cli/v3/hooks-migration/
|
|
18282
|
+
*/
|
|
18283
|
+
const KIRO_AGENT_CONFIG_EVENT_KEYS = /* @__PURE__ */ new Set([...KIRO_HOOK_EVENTS, ...KIRO_AGENT_CONFIG_NATIVE_EVENT_NAMES]);
|
|
18284
|
+
function canonicalToKiroHooks({ config, logger }) {
|
|
17650
18285
|
const overrideKey = "kiro";
|
|
17651
18286
|
const kiroSupported = new Set(KIRO_HOOK_EVENTS);
|
|
17652
18287
|
const sharedHooks = {};
|
|
17653
18288
|
for (const [event, defs] of Object.entries(config.hooks)) if (kiroSupported.has(event)) sharedHooks[event] = defs;
|
|
18289
|
+
const overrideHooks = {};
|
|
18290
|
+
const droppedEvents = [];
|
|
18291
|
+
for (const [event, defs] of Object.entries(config[overrideKey]?.hooks ?? {})) if (KIRO_AGENT_CONFIG_EVENT_KEYS.has(event)) overrideHooks[event] = defs;
|
|
18292
|
+
else droppedEvents.push(event);
|
|
18293
|
+
if (droppedEvents.length > 0) logger?.warn(`Skipped hook event(s) from the "kiro" override block for the deprecated kiro agent config (no event key of that format): ${droppedEvents.join(", ")}. They are emitted for the kiro-cli / kiro-ide targets, which read the same block.`);
|
|
17654
18294
|
const effectiveHooks = {
|
|
17655
18295
|
...sharedHooks,
|
|
17656
|
-
...
|
|
18296
|
+
...overrideHooks
|
|
17657
18297
|
};
|
|
17658
18298
|
const kiro = {};
|
|
17659
18299
|
for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
|
|
@@ -17748,11 +18388,14 @@ var KiroHooks = class KiroHooks extends ToolHooks {
|
|
|
17748
18388
|
validate
|
|
17749
18389
|
});
|
|
17750
18390
|
}
|
|
17751
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
18391
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
17752
18392
|
const paths = KiroHooks.getSettablePaths({ global });
|
|
17753
18393
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
17754
18394
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
17755
|
-
const kiroHooks = canonicalToKiroHooks(
|
|
18395
|
+
const kiroHooks = canonicalToKiroHooks({
|
|
18396
|
+
config: rulesyncHooks.getJson(),
|
|
18397
|
+
logger
|
|
18398
|
+
});
|
|
17756
18399
|
const fileContent = applySharedConfigPatch({
|
|
17757
18400
|
fileKey: sharedConfigFileKey(paths),
|
|
17758
18401
|
feature: "hooks",
|
|
@@ -17868,6 +18511,30 @@ const PI_TOOL_EVENTS = /* @__PURE__ */ new Set(["tool_call", "tool_result"]);
|
|
|
17868
18511
|
*/
|
|
17869
18512
|
const PI_ASSISTANT_MESSAGE_EVENTS = /* @__PURE__ */ new Set(["message_end"]);
|
|
17870
18513
|
/**
|
|
18514
|
+
* `tool_call` is the only Pi extension event that can block, and it is Pi's
|
|
18515
|
+
* only tool gate. Its return contract is
|
|
18516
|
+
* `{ block: true, reason?: string, terminate?: boolean }`.
|
|
18517
|
+
*
|
|
18518
|
+
* @see https://github.com/earendil-works/pi/blob/v0.84.1/packages/coding-agent/docs/extensions.md#tool_call
|
|
18519
|
+
*/
|
|
18520
|
+
const PI_BLOCKING_EVENT = "tool_call";
|
|
18521
|
+
/**
|
|
18522
|
+
* Helper emitted alongside blocking handlers. `promisify(exec)` rejects on a
|
|
18523
|
+
* non-zero exit with an error carrying `stdout` / `stderr` / `code`, so the
|
|
18524
|
+
* reason is derived from the rejection rather than from a resolved exit code.
|
|
18525
|
+
*/
|
|
18526
|
+
const BLOCK_REASON_HELPER_LINES = [
|
|
18527
|
+
"function toBlockReason(error: unknown): string {",
|
|
18528
|
+
" const result = error as { stdout?: unknown; stderr?: unknown; code?: unknown } | null;",
|
|
18529
|
+
" const stderr = String(result?.stderr ?? \"\").trim();",
|
|
18530
|
+
" if (stderr) return stderr;",
|
|
18531
|
+
" const stdout = String(result?.stdout ?? \"\").trim();",
|
|
18532
|
+
" if (stdout) return stdout;",
|
|
18533
|
+
" if (result?.code !== undefined) return `Hook command failed with exit code ${result.code}.`;",
|
|
18534
|
+
" return error instanceof Error ? error.message : String(error);",
|
|
18535
|
+
"}"
|
|
18536
|
+
];
|
|
18537
|
+
/**
|
|
17871
18538
|
* Validate a hook matcher as a regular expression and return it as a JS
|
|
17872
18539
|
* string-literal (JSON.stringify quoting) safe to embed in generated code.
|
|
17873
18540
|
*/
|
|
@@ -17904,6 +18571,22 @@ function collectPiHandlers({ effectiveHooks, eventMap }) {
|
|
|
17904
18571
|
}
|
|
17905
18572
|
return handlerGroups;
|
|
17906
18573
|
}
|
|
18574
|
+
function buildCommandLines({ handler, usesToolName, blocksToolCall }) {
|
|
18575
|
+
const lines = [];
|
|
18576
|
+
const gated = usesToolName && Boolean(handler.matcher);
|
|
18577
|
+
const indent = gated ? " " : " ";
|
|
18578
|
+
const embeddedCommand = JSON.stringify(handler.command);
|
|
18579
|
+
if (gated && handler.matcher) lines.push(` if (new RegExp(${matcherToEmbeddedLiteral(handler.matcher)}).test(event.toolName)) {`);
|
|
18580
|
+
if (blocksToolCall) {
|
|
18581
|
+
lines.push(`${indent}try {`);
|
|
18582
|
+
lines.push(`${indent} await run(${embeddedCommand});`);
|
|
18583
|
+
lines.push(`${indent}} catch (error) {`);
|
|
18584
|
+
lines.push(`${indent} return { block: true, reason: toBlockReason(error) };`);
|
|
18585
|
+
lines.push(`${indent}}`);
|
|
18586
|
+
} else lines.push(`${indent}await run(${embeddedCommand});`);
|
|
18587
|
+
if (gated) lines.push(" }");
|
|
18588
|
+
return lines;
|
|
18589
|
+
}
|
|
17907
18590
|
function buildSubscriptionLines(handlerGroups) {
|
|
17908
18591
|
const lines = [];
|
|
17909
18592
|
for (const [piEvent, handlers] of Object.entries(handlerGroups)) {
|
|
@@ -17912,14 +18595,11 @@ function buildSubscriptionLines(handlerGroups) {
|
|
|
17912
18595
|
const usesEvent = usesToolName || gatesOnAssistant;
|
|
17913
18596
|
lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${usesEvent ? "event" : ""}) => {`);
|
|
17914
18597
|
if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
|
|
17915
|
-
for (const handler of handlers) {
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
lines.push(" }");
|
|
17921
|
-
} else lines.push(` await run(${embeddedCommand});`);
|
|
17922
|
-
}
|
|
18598
|
+
for (const handler of handlers) lines.push(...buildCommandLines({
|
|
18599
|
+
handler,
|
|
18600
|
+
usesToolName,
|
|
18601
|
+
blocksToolCall: piEvent === PI_BLOCKING_EVENT
|
|
18602
|
+
}));
|
|
17923
18603
|
lines.push(" });");
|
|
17924
18604
|
}
|
|
17925
18605
|
return lines;
|
|
@@ -17928,8 +18608,9 @@ function buildSubscriptionLines(handlerGroups) {
|
|
|
17928
18608
|
* Generate the rulesync-owned Pi extension (a TypeScript module with a
|
|
17929
18609
|
* default-export factory receiving Pi's ExtensionAPI) that subscribes to the
|
|
17930
18610
|
* mapped lifecycle events and executes the configured hook commands via the
|
|
17931
|
-
* platform shell.
|
|
17932
|
-
*
|
|
18611
|
+
* platform shell. Handlers observe events, except on `tool_call` — Pi's only
|
|
18612
|
+
* blocking event and its only tool gate — where a hook command that exits
|
|
18613
|
+
* non-zero denies the call with `{ block: true, reason }`.
|
|
17933
18614
|
*
|
|
17934
18615
|
* @see https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md
|
|
17935
18616
|
*/
|
|
@@ -17941,10 +18622,12 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap }) {
|
|
|
17941
18622
|
};
|
|
17942
18623
|
const effectiveHooks = {};
|
|
17943
18624
|
for (const [event, defs] of Object.entries(configHooks)) if (supported.has(event)) effectiveHooks[event] = defs;
|
|
17944
|
-
const
|
|
18625
|
+
const handlerGroups = collectPiHandlers({
|
|
17945
18626
|
effectiveHooks,
|
|
17946
18627
|
eventMap
|
|
17947
|
-
})
|
|
18628
|
+
});
|
|
18629
|
+
const subscriptionLines = buildSubscriptionLines(handlerGroups);
|
|
18630
|
+
const needsBlockReasonHelper = Boolean(handlerGroups[PI_BLOCKING_EVENT]);
|
|
17948
18631
|
const lines = ["// Generated by rulesync. Do not edit manually."];
|
|
17949
18632
|
if (subscriptionLines.length === 0) {
|
|
17950
18633
|
lines.push("export default function () {}");
|
|
@@ -17958,6 +18641,10 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap }) {
|
|
|
17958
18641
|
lines.push("");
|
|
17959
18642
|
lines.push("const run = promisify(exec);");
|
|
17960
18643
|
lines.push("");
|
|
18644
|
+
if (needsBlockReasonHelper) {
|
|
18645
|
+
lines.push(...BLOCK_REASON_HELPER_LINES);
|
|
18646
|
+
lines.push("");
|
|
18647
|
+
}
|
|
17961
18648
|
lines.push("export default function (pi: ExtensionAPI) {");
|
|
17962
18649
|
lines.push(...subscriptionLines);
|
|
17963
18650
|
lines.push("}");
|
|
@@ -18687,6 +19374,20 @@ function unsupportedMatcherEventNames({ factory, effectiveHooks }) {
|
|
|
18687
19374
|
}
|
|
18688
19375
|
return [...eventsWithMatcher];
|
|
18689
19376
|
}
|
|
19377
|
+
/**
|
|
19378
|
+
* Targets that read their tool-scoped `{key}.hooks` override from a differently
|
|
19379
|
+
* named key. The two Kiro standalone-format targets write the very same
|
|
19380
|
+
* `.kiro/hooks/rulesync.json`, so they share the `kiro` block: a per-target
|
|
19381
|
+
* block would make that one file's content depend on generation order. The same
|
|
19382
|
+
* resolution is used by the MCP (`MCP_BLOCK_KEY_ALIASES`) and permissions
|
|
19383
|
+
* (`PERMISSION_OVERRIDE_KEY_ALIASES`) features for the files they share.
|
|
19384
|
+
*/
|
|
19385
|
+
const HOOKS_OVERRIDE_KEY_ALIASES = {
|
|
19386
|
+
"kiro-cli": KIRO_HOOKS_OVERRIDE_KEY,
|
|
19387
|
+
"kiro-ide": KIRO_HOOKS_OVERRIDE_KEY
|
|
19388
|
+
};
|
|
19389
|
+
/** The targets writing the standalone `.kiro/hooks/*.json` v1 format. */
|
|
19390
|
+
const KIRO_STANDALONE_HOOKS_TARGETS = /* @__PURE__ */ new Set(["kiro-cli", "kiro-ide"]);
|
|
18690
19391
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
18691
19392
|
["amp", {
|
|
18692
19393
|
class: AmpHooks,
|
|
@@ -18927,7 +19628,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
18927
19628
|
},
|
|
18928
19629
|
supportedEvents: KIRO_HOOK_EVENTS,
|
|
18929
19630
|
supportedHookTypes: ["command"],
|
|
18930
|
-
supportsMatcher: true
|
|
19631
|
+
supportsMatcher: true,
|
|
19632
|
+
passthroughOverrideEvents: true
|
|
18931
19633
|
}],
|
|
18932
19634
|
["kiro-cli", {
|
|
18933
19635
|
class: KiroCliHooks,
|
|
@@ -19107,7 +19809,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
19107
19809
|
if (!factory) throw new Error(`Unsupported tool target: ${this.toolTarget}`);
|
|
19108
19810
|
const config = rulesyncHooks.getJson();
|
|
19109
19811
|
const sharedHooks = config.hooks;
|
|
19110
|
-
const overrideHooks = config[this.toolTarget]?.hooks ?? {};
|
|
19812
|
+
const overrideHooks = config[HOOKS_OVERRIDE_KEY_ALIASES[this.toolTarget] ?? this.toolTarget]?.hooks ?? {};
|
|
19111
19813
|
const effectiveHooks = {
|
|
19112
19814
|
...sharedHooks,
|
|
19113
19815
|
...overrideHooks
|
|
@@ -19133,14 +19835,14 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
19133
19835
|
}
|
|
19134
19836
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
19135
19837
|
}
|
|
19136
|
-
if (this.toolTarget
|
|
19838
|
+
if (!KIRO_STANDALONE_HOOKS_TARGETS.has(this.toolTarget)) {
|
|
19137
19839
|
const skippedEvents = new Set(unsupportedEventNames({
|
|
19138
19840
|
factory,
|
|
19139
19841
|
sharedHooks,
|
|
19140
19842
|
effectiveHooks
|
|
19141
19843
|
}));
|
|
19142
19844
|
const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
|
|
19143
|
-
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only kiro-ide supports the flag): ${eventsWithDisabledHooks.join(", ")}`);
|
|
19845
|
+
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the kiro-cli / kiro-ide standalone hooks format supports the flag): ${eventsWithDisabledHooks.join(", ")}`);
|
|
19144
19846
|
}
|
|
19145
19847
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
19146
19848
|
factory,
|
|
@@ -19639,9 +20341,11 @@ var CursorIgnore = class CursorIgnore extends ToolIgnore {
|
|
|
19639
20341
|
*
|
|
19640
20342
|
* Generates the brand-aligned `.devinignore` file with gitignore-compatible
|
|
19641
20343
|
* syntax. Devin automatically respects `.gitignore` patterns and has built-in
|
|
19642
|
-
* defaults for node_modules/ and hidden files. On import, the
|
|
19643
|
-
* `.codeiumignore`
|
|
19644
|
-
* round-trip.
|
|
20344
|
+
* defaults for node_modules/ and hidden files. On import, the pre-rebrand
|
|
20345
|
+
* `.codeiumignore` and `.windsurfignore` filenames are read as fallbacks so
|
|
20346
|
+
* existing projects still round-trip. The docs list the three names side by
|
|
20347
|
+
* side without defining a precedence between them, so the order below is
|
|
20348
|
+
* rulesync's own choice: brand-aligned name first, then the legacy names.
|
|
19645
20349
|
*
|
|
19646
20350
|
* In global mode the enterprise-wide `~/.codeium/.codeiumignore` is written
|
|
19647
20351
|
* instead; see `DEVIN_GLOBAL_IGNORE_DIR_PATH` for why that path keeps the
|
|
@@ -19680,9 +20384,16 @@ var DevinIgnore = class DevinIgnore extends ToolIgnore {
|
|
|
19680
20384
|
validate,
|
|
19681
20385
|
global
|
|
19682
20386
|
});
|
|
19683
|
-
const
|
|
19684
|
-
|
|
19685
|
-
|
|
20387
|
+
const candidateFileNames = [
|
|
20388
|
+
relativeFilePath,
|
|
20389
|
+
DEVIN_LEGACY_IGNORE_FILE_NAME,
|
|
20390
|
+
DEVIN_WINDSURF_IGNORE_FILE_NAME
|
|
20391
|
+
];
|
|
20392
|
+
let resolvedFilePath = relativeFilePath;
|
|
20393
|
+
for (const candidateFileName of candidateFileNames) if (await fileExists(join(outputRoot, relativeDirPath, candidateFileName))) {
|
|
20394
|
+
resolvedFilePath = candidateFileName;
|
|
20395
|
+
break;
|
|
20396
|
+
}
|
|
19686
20397
|
const fileContent = await readFileContent(join(outputRoot, relativeDirPath, resolvedFilePath));
|
|
19687
20398
|
return new DevinIgnore({
|
|
19688
20399
|
outputRoot,
|
|
@@ -22911,8 +23622,8 @@ function convertToGoosePluginMcpServers(mcpServers, logger) {
|
|
|
22911
23622
|
* shape and cannot express `url`/`headers`, so remote servers are skipped with
|
|
22912
23623
|
* a warning in project mode (use `--global` to sync them instead).
|
|
22913
23624
|
*
|
|
22914
|
-
* @see https://
|
|
22915
|
-
* @see https://github.com/
|
|
23625
|
+
* @see https://goose-docs.ai/docs/getting-started/using-extensions/
|
|
23626
|
+
* @see https://github.com/aaif-goose/goose/pull/9471
|
|
22916
23627
|
*/
|
|
22917
23628
|
var GooseMcp = class GooseMcp extends ToolMcp {
|
|
22918
23629
|
config;
|
|
@@ -23183,7 +23894,7 @@ function resolveHermesTimeout(config) {
|
|
|
23183
23894
|
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
23184
23895
|
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
23185
23896
|
* `connect_timeout` (seconds), `supports_parallel_tool_calls`,
|
|
23186
|
-
* `keepalive_interval`, and `
|
|
23897
|
+
* `keepalive_interval`, `elicitation`, `trust`, and `identity_header` — verbatim
|
|
23187
23898
|
* from `source` to `target`. Field names are identical on both sides (the
|
|
23188
23899
|
* canonical `McpServerSchema` is a `looseObject`), so this serves export and
|
|
23189
23900
|
* import alike. See the Hermes mcp-config-reference.
|
|
@@ -23252,6 +23963,14 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
23252
23963
|
target.elicitation = omitPrototypePollutionKeys(structuredClone(source.elicitation));
|
|
23253
23964
|
copied = true;
|
|
23254
23965
|
}
|
|
23966
|
+
if (typeof source.trust === "string") {
|
|
23967
|
+
target.trust = source.trust;
|
|
23968
|
+
copied = true;
|
|
23969
|
+
}
|
|
23970
|
+
if (isPlainObject$1(source.identity_header)) {
|
|
23971
|
+
target.identity_header = omitPrototypePollutionKeys(structuredClone(source.identity_header));
|
|
23972
|
+
copied = true;
|
|
23973
|
+
}
|
|
23255
23974
|
return copied;
|
|
23256
23975
|
}
|
|
23257
23976
|
/**
|
|
@@ -24416,6 +25135,213 @@ var KiroMcp = class KiroMcp extends ToolMcp {
|
|
|
24416
25135
|
}
|
|
24417
25136
|
};
|
|
24418
25137
|
//#endregion
|
|
25138
|
+
//#region src/constants/musecode-paths.ts
|
|
25139
|
+
const MUSECODE_RULE_FILE_NAME = "AGENTS.md";
|
|
25140
|
+
const MUSECODE_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
25141
|
+
const MUSECODE_GLOBAL_CONFIG_DIR_PATH = join(".config", "muse");
|
|
25142
|
+
const MUSECODE_GLOBAL_SKILLS_DIR_PATH = join(MUSECODE_GLOBAL_CONFIG_DIR_PATH, "skills");
|
|
25143
|
+
const MUSECODE_SETTINGS_FILE_NAME = "settings.json";
|
|
25144
|
+
//#endregion
|
|
25145
|
+
//#region src/features/mcp/musecode-mcp.ts
|
|
25146
|
+
const MUSECODE_GLOBAL_ONLY_MESSAGE = "Muse Code MCP is global-only; use --global to sync ~/.config/muse/settings.json";
|
|
25147
|
+
/**
|
|
25148
|
+
* Single spelling of the settings.json codec/policy, matching the
|
|
25149
|
+
* `SHARED_CONFIG_OWNERSHIP` declaration for `.config/muse/settings.json`:
|
|
25150
|
+
* fail closed on an unparseable root rather than replacing the user's primary
|
|
25151
|
+
* Muse Code config with generated output.
|
|
25152
|
+
*/
|
|
25153
|
+
function parseMusecodeSettings(fileContent, filePath) {
|
|
25154
|
+
return parseSharedConfig({
|
|
25155
|
+
format: "json",
|
|
25156
|
+
fileContent,
|
|
25157
|
+
filePath,
|
|
25158
|
+
invalidRootPolicy: "error"
|
|
25159
|
+
});
|
|
25160
|
+
}
|
|
25161
|
+
/**
|
|
25162
|
+
* Convert canonical rulesync servers to Muse Code's native `mcp_servers` shape.
|
|
25163
|
+
* Each entry carries a `transport` discriminator: `stdio` servers spawn a
|
|
25164
|
+
* `command` (single string) with `args`/`env`, and `streamable_http` servers
|
|
25165
|
+
* are reached at a `url` with optional `headers`. Only documented fields are
|
|
25166
|
+
* emitted; a canonical `disabled: true` maps to Muse's `enabled: false`.
|
|
25167
|
+
*/
|
|
25168
|
+
function convertToMusecodeFormat(mcpServers, logger) {
|
|
25169
|
+
const result = {};
|
|
25170
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
25171
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
25172
|
+
if (!isRecord$1(config)) continue;
|
|
25173
|
+
if (declaresNoTransport(config)) {
|
|
25174
|
+
warnAndSkipMcpServer({
|
|
25175
|
+
toolName: "Muse Code",
|
|
25176
|
+
serverName: name,
|
|
25177
|
+
reason: "no transport",
|
|
25178
|
+
logger
|
|
25179
|
+
});
|
|
25180
|
+
continue;
|
|
25181
|
+
}
|
|
25182
|
+
const converted = {};
|
|
25183
|
+
if (isRemoteMcpServer(config)) {
|
|
25184
|
+
const url = resolveRemoteMcpUrl(config);
|
|
25185
|
+
if (!url) {
|
|
25186
|
+
warnAndSkipMcpServer({
|
|
25187
|
+
toolName: "Muse Code",
|
|
25188
|
+
serverName: name,
|
|
25189
|
+
reason: "a remote transport without a url",
|
|
25190
|
+
logger
|
|
25191
|
+
});
|
|
25192
|
+
continue;
|
|
25193
|
+
}
|
|
25194
|
+
const stated = config.type ?? config.transport;
|
|
25195
|
+
if (stated === "sse" || stated === "ws" || stated === void 0 && /^wss?:\/\//i.test(url)) {
|
|
25196
|
+
warnAndSkipMcpServer({
|
|
25197
|
+
toolName: "Muse Code",
|
|
25198
|
+
serverName: name,
|
|
25199
|
+
reason: `the "${stated ?? "ws"}" transport, which Muse Code does not implement (only stdio and streamable_http are supported)`,
|
|
25200
|
+
logger
|
|
25201
|
+
});
|
|
25202
|
+
continue;
|
|
25203
|
+
}
|
|
25204
|
+
converted.transport = "streamable_http";
|
|
25205
|
+
converted.url = url;
|
|
25206
|
+
if (config.headers && Object.keys(config.headers).length > 0) converted.headers = config.headers;
|
|
25207
|
+
} else {
|
|
25208
|
+
const [command, ...args] = resolveLocalMcpCommand(config);
|
|
25209
|
+
if (!command) {
|
|
25210
|
+
warnAndSkipMcpServer({
|
|
25211
|
+
toolName: "Muse Code",
|
|
25212
|
+
serverName: name,
|
|
25213
|
+
reason: "a stdio transport without a command",
|
|
25214
|
+
logger
|
|
25215
|
+
});
|
|
25216
|
+
continue;
|
|
25217
|
+
}
|
|
25218
|
+
converted.transport = "stdio";
|
|
25219
|
+
converted.command = command;
|
|
25220
|
+
converted.args = args;
|
|
25221
|
+
if (config.env && Object.keys(config.env).length > 0) converted.env = config.env;
|
|
25222
|
+
}
|
|
25223
|
+
if (config.disabled === true) converted.enabled = false;
|
|
25224
|
+
result[name] = converted;
|
|
25225
|
+
}
|
|
25226
|
+
return result;
|
|
25227
|
+
}
|
|
25228
|
+
/**
|
|
25229
|
+
* Convert Muse Code's native `mcp_servers` shape back to canonical rulesync
|
|
25230
|
+
* servers. The `transport` discriminator is dropped (`streamable_http` is not a
|
|
25231
|
+
* canonical enum value; the transport is re-derived from `command`/`url` on the
|
|
25232
|
+
* next generate), `enabled: false` maps back to `disabled: true`, and unknown
|
|
25233
|
+
* keys (e.g. `mode`, `framing`) pass through untouched.
|
|
25234
|
+
*/
|
|
25235
|
+
function convertFromMusecodeFormat(musecodeMcp) {
|
|
25236
|
+
const result = {};
|
|
25237
|
+
for (const [name, config] of Object.entries(musecodeMcp)) {
|
|
25238
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
25239
|
+
const converted = {};
|
|
25240
|
+
for (const [key, value] of Object.entries(config)) {
|
|
25241
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
25242
|
+
if (key === "transport") continue;
|
|
25243
|
+
if (key === "enabled") {
|
|
25244
|
+
if (value === false) converted.disabled = true;
|
|
25245
|
+
continue;
|
|
25246
|
+
}
|
|
25247
|
+
converted[key] = value;
|
|
25248
|
+
}
|
|
25249
|
+
result[name] = converted;
|
|
25250
|
+
}
|
|
25251
|
+
return result;
|
|
25252
|
+
}
|
|
25253
|
+
/**
|
|
25254
|
+
* Meta Muse Code MCP servers.
|
|
25255
|
+
*
|
|
25256
|
+
* Muse Code reads MCP servers only from the `mcp_servers` block of the GLOBAL
|
|
25257
|
+
* user settings file `~/.config/muse/settings.json`; no project-scoped MCP
|
|
25258
|
+
* location is documented. The settings file must carry
|
|
25259
|
+
* `"schema_version": 1` — a file that omits that key fails every command at
|
|
25260
|
+
* startup with `malformed settings file` — so the key is bootstrapped when the
|
|
25261
|
+
* file is created and preserved when it already exists. Other settings keys are
|
|
25262
|
+
* preserved via the shared-config gateway, and the file is never deleted.
|
|
25263
|
+
*
|
|
25264
|
+
* @see https://dev.meta.ai/docs/muse-code/configuration.md
|
|
25265
|
+
* @see https://dev.meta.ai/docs/muse-code/extending.md
|
|
25266
|
+
*/
|
|
25267
|
+
var MusecodeMcp = class MusecodeMcp extends ToolMcp {
|
|
25268
|
+
json;
|
|
25269
|
+
constructor(params) {
|
|
25270
|
+
super(params);
|
|
25271
|
+
this.json = parseMusecodeSettings(this.fileContent ?? "", join(this.relativeDirPath, this.relativeFilePath));
|
|
25272
|
+
}
|
|
25273
|
+
getJson() {
|
|
25274
|
+
return this.json;
|
|
25275
|
+
}
|
|
25276
|
+
isDeletable() {
|
|
25277
|
+
return false;
|
|
25278
|
+
}
|
|
25279
|
+
static getSettablePaths(_options) {
|
|
25280
|
+
return {
|
|
25281
|
+
relativeDirPath: MUSECODE_GLOBAL_CONFIG_DIR_PATH,
|
|
25282
|
+
relativeFilePath: MUSECODE_SETTINGS_FILE_NAME
|
|
25283
|
+
};
|
|
25284
|
+
}
|
|
25285
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
25286
|
+
if (!global) throw new Error(MUSECODE_GLOBAL_ONLY_MESSAGE);
|
|
25287
|
+
const paths = this.getSettablePaths({ global });
|
|
25288
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
25289
|
+
return new MusecodeMcp({
|
|
25290
|
+
outputRoot,
|
|
25291
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25292
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25293
|
+
fileContent,
|
|
25294
|
+
validate,
|
|
25295
|
+
global
|
|
25296
|
+
});
|
|
25297
|
+
}
|
|
25298
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
25299
|
+
if (!global) throw new Error(MUSECODE_GLOBAL_ONLY_MESSAGE);
|
|
25300
|
+
const paths = this.getSettablePaths({ global });
|
|
25301
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25302
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
25303
|
+
const existing = parseMusecodeSettings(existingContent, filePath);
|
|
25304
|
+
const converted = convertToMusecodeFormat(rulesyncMcp.getMcpServers(), logger);
|
|
25305
|
+
return new MusecodeMcp({
|
|
25306
|
+
outputRoot,
|
|
25307
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25308
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25309
|
+
fileContent: applySharedConfigPatch({
|
|
25310
|
+
fileKey: sharedConfigFileKey(paths),
|
|
25311
|
+
feature: "mcp",
|
|
25312
|
+
existingContent,
|
|
25313
|
+
patch: {
|
|
25314
|
+
mcp_servers: converted,
|
|
25315
|
+
...existing.schema_version === void 0 && { schema_version: 1 }
|
|
25316
|
+
},
|
|
25317
|
+
filePath
|
|
25318
|
+
}),
|
|
25319
|
+
validate,
|
|
25320
|
+
global
|
|
25321
|
+
});
|
|
25322
|
+
}
|
|
25323
|
+
toRulesyncMcp() {
|
|
25324
|
+
const converted = convertFromMusecodeFormat(isRecord$1(this.json.mcp_servers) ? this.json.mcp_servers : {});
|
|
25325
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: converted }, null, 2) });
|
|
25326
|
+
}
|
|
25327
|
+
validate() {
|
|
25328
|
+
return {
|
|
25329
|
+
success: true,
|
|
25330
|
+
error: null
|
|
25331
|
+
};
|
|
25332
|
+
}
|
|
25333
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
25334
|
+
return new MusecodeMcp({
|
|
25335
|
+
outputRoot,
|
|
25336
|
+
relativeDirPath,
|
|
25337
|
+
relativeFilePath,
|
|
25338
|
+
fileContent: JSON.stringify({ mcp_servers: {} }, null, 2),
|
|
25339
|
+
validate: false,
|
|
25340
|
+
global
|
|
25341
|
+
});
|
|
25342
|
+
}
|
|
25343
|
+
};
|
|
25344
|
+
//#endregion
|
|
24419
25345
|
//#region src/features/mcp/opencode-mcp.ts
|
|
24420
25346
|
const OPENCODE_ENV_VAR_PATTERN = /(?<!\$)\{env:([^}:]+)\}/g;
|
|
24421
25347
|
const OpencodeMcpLocalServerSchema = z.looseObject({
|
|
@@ -26372,6 +27298,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
26372
27298
|
supportsDisabledTools: false
|
|
26373
27299
|
}
|
|
26374
27300
|
}],
|
|
27301
|
+
["musecode", {
|
|
27302
|
+
class: MusecodeMcp,
|
|
27303
|
+
meta: {
|
|
27304
|
+
supportsProject: false,
|
|
27305
|
+
supportsGlobal: true,
|
|
27306
|
+
supportsEnabledTools: false,
|
|
27307
|
+
supportsDisabledTools: false
|
|
27308
|
+
}
|
|
27309
|
+
}],
|
|
26375
27310
|
["opencode", {
|
|
26376
27311
|
class: OpencodeMcp,
|
|
26377
27312
|
meta: {
|
|
@@ -29187,6 +30122,163 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
29187
30122
|
}
|
|
29188
30123
|
};
|
|
29189
30124
|
//#endregion
|
|
30125
|
+
//#region src/features/permissions/copilotcli-permissions.ts
|
|
30126
|
+
/**
|
|
30127
|
+
* The only canonical category the Copilot CLI settings can express: URL
|
|
30128
|
+
* approvals. Every other category (`bash`, `edit`, `read`, ...) has no
|
|
30129
|
+
* user-authorable surface — the CLI's `permissions.allow`/`ask`/`deny` rule
|
|
30130
|
+
* arrays are accepted only in MDM/enterprise managed settings, and session
|
|
30131
|
+
* tool approvals are machine-written to `permissions-config.json`.
|
|
30132
|
+
*/
|
|
30133
|
+
const WEBFETCH_CATEGORY = "webfetch";
|
|
30134
|
+
/** `~/.copilot/settings.json` and `.github/copilot/settings.json` keys. */
|
|
30135
|
+
const ALLOWED_URLS_KEY = "allowedUrls";
|
|
30136
|
+
const DENIED_URLS_KEY = "deniedUrls";
|
|
30137
|
+
function toUrlList(value) {
|
|
30138
|
+
if (!Array.isArray(value)) return [];
|
|
30139
|
+
return value.filter((entry) => typeof entry === "string");
|
|
30140
|
+
}
|
|
30141
|
+
/**
|
|
30142
|
+
* Split one canonical category's rules into the two URL lists. `ask` entries
|
|
30143
|
+
* are omitted: the CLI prompts for any URL that is in neither list, which is
|
|
30144
|
+
* exactly what `ask` means.
|
|
30145
|
+
*/
|
|
30146
|
+
function splitUrlRules(rules) {
|
|
30147
|
+
const allowedUrls = [];
|
|
30148
|
+
const deniedUrls = [];
|
|
30149
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow") allowedUrls.push(pattern);
|
|
30150
|
+
else if (action === "deny") deniedUrls.push(pattern);
|
|
30151
|
+
return {
|
|
30152
|
+
allowedUrls,
|
|
30153
|
+
deniedUrls
|
|
30154
|
+
};
|
|
30155
|
+
}
|
|
30156
|
+
/**
|
|
30157
|
+
* Permissions generator for the GitHub Copilot CLI.
|
|
30158
|
+
*
|
|
30159
|
+
* The CLI keeps two persistent settings files: the user-scope
|
|
30160
|
+
* `~/.copilot/settings.json` and the repository-scope
|
|
30161
|
+
* `.github/copilot/settings.json` (shipped in v1.0.60). Both are shared,
|
|
30162
|
+
* hand-edited files carrying unrelated keys (`model`, `effortLevel`, `hooks`,
|
|
30163
|
+
* `sandbox.*`, ...), so writes go through the shared-config gateway, only the
|
|
30164
|
+
* URL keys are owned, and the file is never deleted.
|
|
30165
|
+
*
|
|
30166
|
+
* The canonical `webfetch` category maps onto the CLI's two URL lists:
|
|
30167
|
+
* `allow` → `allowedUrls`, `deny` → `deniedUrls`, `ask` → the pattern is
|
|
30168
|
+
* omitted so the CLI falls through to its approval prompt.
|
|
30169
|
+
*
|
|
30170
|
+
* Scope asymmetry: the repository-scope key table documents `deniedUrls`
|
|
30171
|
+
* (union — a repository may add entries, never remove them) but NOT
|
|
30172
|
+
* `allowedUrls`, so an allow rule is only enforceable at user scope. At project
|
|
30173
|
+
* scope allow rules are therefore dropped with a warning rather than written to
|
|
30174
|
+
* a key the CLI ignores. (v1.0.79 additionally warns on startup about unknown
|
|
30175
|
+
* top-level keys in the user `settings.json`, so only documented keys are ever
|
|
30176
|
+
* emitted there either.)
|
|
30177
|
+
*
|
|
30178
|
+
* @see https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
30179
|
+
* @see https://github.com/github/copilot-cli/blob/main/changelog.md
|
|
30180
|
+
*/
|
|
30181
|
+
var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions {
|
|
30182
|
+
constructor(params) {
|
|
30183
|
+
super({
|
|
30184
|
+
...params,
|
|
30185
|
+
fileContent: params.fileContent ?? "{}"
|
|
30186
|
+
});
|
|
30187
|
+
}
|
|
30188
|
+
/**
|
|
30189
|
+
* `settings.json` holds unrelated user settings (`model`, `effortLevel`,
|
|
30190
|
+
* `hooks`, ...), so it must not be deleted.
|
|
30191
|
+
*/
|
|
30192
|
+
isDeletable() {
|
|
30193
|
+
return false;
|
|
30194
|
+
}
|
|
30195
|
+
static getSettablePaths({ global = false } = {}) {
|
|
30196
|
+
return global ? {
|
|
30197
|
+
relativeDirPath: COPILOT_DIR,
|
|
30198
|
+
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
30199
|
+
} : {
|
|
30200
|
+
relativeDirPath: COPILOTCLI_PROJECT_SETTINGS_DIR_PATH,
|
|
30201
|
+
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
30202
|
+
};
|
|
30203
|
+
}
|
|
30204
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
30205
|
+
const paths = CopilotcliPermissions.getSettablePaths({ global });
|
|
30206
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
30207
|
+
return new CopilotcliPermissions({
|
|
30208
|
+
outputRoot,
|
|
30209
|
+
relativeDirPath: paths.relativeDirPath,
|
|
30210
|
+
relativeFilePath: paths.relativeFilePath,
|
|
30211
|
+
fileContent,
|
|
30212
|
+
validate,
|
|
30213
|
+
global
|
|
30214
|
+
});
|
|
30215
|
+
}
|
|
30216
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
30217
|
+
const paths = CopilotcliPermissions.getSettablePaths({ global });
|
|
30218
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
30219
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
30220
|
+
const rules = rulesyncPermissions.getJson().permission[WEBFETCH_CATEGORY];
|
|
30221
|
+
const patch = {};
|
|
30222
|
+
if (rules !== void 0) {
|
|
30223
|
+
const { allowedUrls, deniedUrls } = splitUrlRules(rules);
|
|
30224
|
+
patch[DENIED_URLS_KEY] = deniedUrls.length > 0 ? deniedUrls : void 0;
|
|
30225
|
+
if (global) patch[ALLOWED_URLS_KEY] = allowedUrls.length > 0 ? allowedUrls : void 0;
|
|
30226
|
+
else if (allowedUrls.length > 0) logger?.warn(`Copilot CLI permissions: dropping ${allowedUrls.length} "webfetch" allow rule(s) at project scope — ${join(paths.relativeDirPath, paths.relativeFilePath)} does not accept "${ALLOWED_URLS_KEY}" (repository settings may only add denials). Author allow rules in global scope (\`--global\`) instead.`);
|
|
30227
|
+
}
|
|
30228
|
+
return new CopilotcliPermissions({
|
|
30229
|
+
outputRoot,
|
|
30230
|
+
relativeDirPath: paths.relativeDirPath,
|
|
30231
|
+
relativeFilePath: paths.relativeFilePath,
|
|
30232
|
+
fileContent: applySharedConfigPatch({
|
|
30233
|
+
fileKey: sharedConfigFileKey(paths),
|
|
30234
|
+
feature: "permissions",
|
|
30235
|
+
existingContent,
|
|
30236
|
+
patch,
|
|
30237
|
+
filePath
|
|
30238
|
+
}),
|
|
30239
|
+
validate: true,
|
|
30240
|
+
global
|
|
30241
|
+
});
|
|
30242
|
+
}
|
|
30243
|
+
toRulesyncPermissions() {
|
|
30244
|
+
const settings = this.parseSettings();
|
|
30245
|
+
const rules = {};
|
|
30246
|
+
for (const pattern of toUrlList(settings[DENIED_URLS_KEY])) rules[pattern] = "deny";
|
|
30247
|
+
if (this.global) for (const pattern of toUrlList(settings[ALLOWED_URLS_KEY])) rules[pattern] ??= "allow";
|
|
30248
|
+
const permission = Object.keys(rules).length > 0 ? { [WEBFETCH_CATEGORY]: rules } : {};
|
|
30249
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
30250
|
+
}
|
|
30251
|
+
parseSettings() {
|
|
30252
|
+
const relativePath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
30253
|
+
try {
|
|
30254
|
+
return parseSharedConfig({
|
|
30255
|
+
format: "json",
|
|
30256
|
+
fileContent: this.getFileContent() || "{}",
|
|
30257
|
+
filePath: relativePath,
|
|
30258
|
+
invalidRootPolicy: "error"
|
|
30259
|
+
});
|
|
30260
|
+
} catch (error) {
|
|
30261
|
+
throw new Error(`Failed to parse Copilot CLI settings in ${relativePath}: ${formatError(error)}`, { cause: error });
|
|
30262
|
+
}
|
|
30263
|
+
}
|
|
30264
|
+
validate() {
|
|
30265
|
+
return {
|
|
30266
|
+
success: true,
|
|
30267
|
+
error: null
|
|
30268
|
+
};
|
|
30269
|
+
}
|
|
30270
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
30271
|
+
return new CopilotcliPermissions({
|
|
30272
|
+
outputRoot,
|
|
30273
|
+
relativeDirPath,
|
|
30274
|
+
relativeFilePath,
|
|
30275
|
+
fileContent: "{}",
|
|
30276
|
+
validate: false,
|
|
30277
|
+
global
|
|
30278
|
+
});
|
|
30279
|
+
}
|
|
30280
|
+
};
|
|
30281
|
+
//#endregion
|
|
29190
30282
|
//#region src/features/permissions/cursor-permissions.ts
|
|
29191
30283
|
/**
|
|
29192
30284
|
* Mapping from rulesync canonical tool category names (lowercase) to Cursor CLI
|
|
@@ -30204,6 +31296,9 @@ const GROK_TOOL_TO_CATEGORY = {
|
|
|
30204
31296
|
WebSearch: "websearch"
|
|
30205
31297
|
};
|
|
30206
31298
|
const GROK_MCP_TOOL = "MCPTool";
|
|
31299
|
+
const GROK_TOOL_TO_CATEGORY_LOWER = Object.fromEntries(Object.entries(GROK_TOOL_TO_CATEGORY).map(([tool, category]) => [tool.toLowerCase(), category]));
|
|
31300
|
+
const GROK_MCP_TOOL_ALIASES = /* @__PURE__ */ new Set(["mcp", GROK_MCP_TOOL.toLowerCase()]);
|
|
31301
|
+
const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
30207
31302
|
/**
|
|
30208
31303
|
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
30209
31304
|
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
@@ -30256,6 +31351,44 @@ function parseGrokEntry(entry) {
|
|
|
30256
31351
|
};
|
|
30257
31352
|
}
|
|
30258
31353
|
/**
|
|
31354
|
+
* Parse one entry of the verbose `[[permission.rules]]` form
|
|
31355
|
+
* (`{ action = "allow", tool = "bash", pattern = "git *" }`) into a canonical
|
|
31356
|
+
* category + pattern + action. Unlike the compact array entries these are TOML
|
|
31357
|
+
* tables, so the tool name arrives as its own field instead of an entry string.
|
|
31358
|
+
* Returns `null` for malformed entries and for tools with no canonical
|
|
31359
|
+
* equivalent, so callers can skip them (the whole `rules` array is preserved
|
|
31360
|
+
* verbatim on generate regardless).
|
|
31361
|
+
*/
|
|
31362
|
+
function parseGrokRule(rule) {
|
|
31363
|
+
if (!isRecord$1(rule)) return null;
|
|
31364
|
+
const { action: rawAction, tool, pattern } = rule;
|
|
31365
|
+
const parsedAction = PermissionActionSchema.safeParse(rawAction);
|
|
31366
|
+
if (!parsedAction.success) return null;
|
|
31367
|
+
const action = parsedAction.data;
|
|
31368
|
+
if (typeof tool !== "string") return null;
|
|
31369
|
+
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
31370
|
+
const trimmedPattern = pattern?.trim() ?? "";
|
|
31371
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$2;
|
|
31372
|
+
const lowerTool = tool.trim().toLowerCase();
|
|
31373
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$2 ? {
|
|
31374
|
+
category: "mcp",
|
|
31375
|
+
pattern: CATCH_ALL_PATTERN$2,
|
|
31376
|
+
action
|
|
31377
|
+
} : {
|
|
31378
|
+
category: `${MCP_CANONICAL_PREFIX$1}${resolvedPattern}`,
|
|
31379
|
+
pattern: CATCH_ALL_PATTERN$2,
|
|
31380
|
+
action
|
|
31381
|
+
};
|
|
31382
|
+
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
31383
|
+
const category = GROK_TOOL_TO_CATEGORY_LOWER[lowerTool];
|
|
31384
|
+
if (category === void 0) return null;
|
|
31385
|
+
return {
|
|
31386
|
+
category,
|
|
31387
|
+
pattern: resolvedPattern,
|
|
31388
|
+
action
|
|
31389
|
+
};
|
|
31390
|
+
}
|
|
31391
|
+
/**
|
|
30259
31392
|
* Permissions adapter for the xAI Grok Build CLI (`grokcli`).
|
|
30260
31393
|
*
|
|
30261
31394
|
* Grok Build CLI ships a Claude-style rule system under `[permission]` in
|
|
@@ -30276,9 +31409,17 @@ function parseGrokEntry(entry) {
|
|
|
30276
31409
|
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
30277
31410
|
* the strictest wins (`deny > ask > allow`) and a warning is logged, so the
|
|
30278
31411
|
* entry never lands contradictorily in two arrays.
|
|
30279
|
-
* - Import:
|
|
30280
|
-
* categories
|
|
30281
|
-
*
|
|
31412
|
+
* - Import: both documented `[permission]` forms are parsed back into
|
|
31413
|
+
* canonical categories — the compact `allow`/`deny`/`ask` arrays and the
|
|
31414
|
+
* verbose `[[permission.rules]]` tables
|
|
31415
|
+
* (`{ action = "allow", tool = "bash", pattern = "git *" }`), whose `tool`
|
|
31416
|
+
* field is matched case-insensitively against the same tool table, plus
|
|
31417
|
+
* the documented `mcp` alias for the compact form's `MCPTool`. Rules
|
|
31418
|
+
* from the two forms are merged with the same `deny > ask > allow`
|
|
31419
|
+
* precedence. When neither form carries a rule (older configs), the coarse
|
|
31420
|
+
* `[ui] permission_mode` is used as a fallback. Generate always emits the
|
|
31421
|
+
* compact arrays, so a user-authored `rules` array is preserved verbatim
|
|
31422
|
+
* but not reconciled against them.
|
|
30282
31423
|
*
|
|
30283
31424
|
* The coarse `[ui] permission_mode` toggle is still written for backward
|
|
30284
31425
|
* compatibility with older Grok versions: `always-approve` when the config is
|
|
@@ -30445,29 +31586,44 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
30445
31586
|
};
|
|
30446
31587
|
}
|
|
30447
31588
|
/**
|
|
30448
|
-
* Parse Grok's `[permission]`
|
|
30449
|
-
*
|
|
30450
|
-
*
|
|
30451
|
-
*
|
|
30452
|
-
*
|
|
31589
|
+
* Parse Grok's `[permission]` section back into a canonical permission map,
|
|
31590
|
+
* reading both supported forms: the compact `allow`/`deny`/`ask` arrays of
|
|
31591
|
+
* Claude-style entries and the verbose `[[permission.rules]]` tables
|
|
31592
|
+
* (`{ action, tool, pattern }`). Returns `null` only when neither form carries
|
|
31593
|
+
* any rule, so the caller can fall back to the coarse `permission_mode`.
|
|
31594
|
+
* Precedence `deny > ask > allow` is applied across both forms, so a tool
|
|
31595
|
+
* described more than once resolves to the strictest action.
|
|
30453
31596
|
*/
|
|
30454
31597
|
function parseGrokPermissionArrays(permission) {
|
|
30455
31598
|
const allow = isStringArray$1(permission.allow) ? permission.allow : void 0;
|
|
30456
31599
|
const deny = isStringArray$1(permission.deny) ? permission.deny : void 0;
|
|
30457
31600
|
const ask = isStringArray$1(permission.ask) ? permission.ask : void 0;
|
|
30458
|
-
|
|
31601
|
+
const rules = Array.isArray(permission[GROKCLI_PERMISSION_RULES_KEY]) ? permission[GROKCLI_PERMISSION_RULES_KEY] : void 0;
|
|
31602
|
+
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
30459
31603
|
const result = {};
|
|
30460
|
-
const
|
|
31604
|
+
const record = ({ category, pattern, action }) => {
|
|
31605
|
+
const bucket = result[category] ??= {};
|
|
31606
|
+
const existing = bucket[pattern];
|
|
31607
|
+
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) bucket[pattern] = action;
|
|
31608
|
+
};
|
|
31609
|
+
const applyEntries = (entries, action) => {
|
|
30461
31610
|
for (const entry of entries ?? []) {
|
|
30462
31611
|
const parsed = parseGrokEntry(entry);
|
|
30463
31612
|
if (parsed === null) continue;
|
|
30464
|
-
|
|
30465
|
-
|
|
31613
|
+
record({
|
|
31614
|
+
...parsed,
|
|
31615
|
+
action
|
|
31616
|
+
});
|
|
30466
31617
|
}
|
|
30467
31618
|
};
|
|
30468
|
-
|
|
30469
|
-
|
|
30470
|
-
|
|
31619
|
+
applyEntries(allow, "allow");
|
|
31620
|
+
applyEntries(ask, "ask");
|
|
31621
|
+
applyEntries(deny, "deny");
|
|
31622
|
+
for (const rule of rules ?? []) {
|
|
31623
|
+
const parsed = parseGrokRule(rule);
|
|
31624
|
+
if (parsed === null) continue;
|
|
31625
|
+
record(parsed);
|
|
31626
|
+
}
|
|
30471
31627
|
return result;
|
|
30472
31628
|
}
|
|
30473
31629
|
/**
|
|
@@ -33152,6 +34308,13 @@ const TAKT_PROVIDER_PROFILES_KEY = "provider_profiles";
|
|
|
33152
34308
|
const TAKT_DEFAULT_PERMISSION_MODE_KEY = "default_permission_mode";
|
|
33153
34309
|
const TAKT_STEP_PERMISSION_OVERRIDES_KEY = "step_permission_overrides";
|
|
33154
34310
|
const TAKT_PROVIDER_OPTIONS_KEY = "provider_options";
|
|
34311
|
+
const TAKT_RUNTIME_PROVIDER_KEY = "provider";
|
|
34312
|
+
const TAKT_RUNTIME_DEFAULTS_KEY = "defaults";
|
|
34313
|
+
const TAKT_RUNTIME_PROFILES_KEY = "profiles";
|
|
34314
|
+
const TAKT_RUNTIME_TARGETS_KEY = "targets";
|
|
34315
|
+
const TAKT_RUNTIME_AUTO_ROUTING_KEY = "auto_routing";
|
|
34316
|
+
const TAKT_RUNTIME_PROFILE_KEY = "profile";
|
|
34317
|
+
const TAKT_RUNTIME_OPTIONS_KEY = "options";
|
|
33155
34318
|
const TAKT_SECURITY_POLICIES = {
|
|
33156
34319
|
workflow_arpeggio: [
|
|
33157
34320
|
"custom_data_source_modules",
|
|
@@ -33204,13 +34367,36 @@ const CATCH_ALL_PATTERN = "*";
|
|
|
33204
34367
|
* profile, layered on top of `default_permission_mode`) and `provider_options`
|
|
33205
34368
|
* (a top-level per-provider sandbox/network table). Both are authored on
|
|
33206
34369
|
* generate and re-extracted on import.
|
|
34370
|
+
*
|
|
34371
|
+
* Takt 0.56.0 moved provider configuration into a separate `runtime.yaml`
|
|
34372
|
+
* (`.takt/runtime.yaml`, `~/.takt/runtime.yaml`). Once its `provider:` section
|
|
34373
|
+
* carries an assignment ("runtime mode" — the state a freshly installed Takt is
|
|
34374
|
+
* in, since it generates an active global runtime.yaml on first launch), any
|
|
34375
|
+
* legacy provider setting left in `config.yaml` makes Takt stop before running
|
|
34376
|
+
* an agent with "Mixed provider configuration detected". rulesync only reads
|
|
34377
|
+
* that file, in two places:
|
|
34378
|
+
* - the active provider is resolved from it first (the `provider_profiles`
|
|
34379
|
+
* key that carries the permission mode is still keyed by provider name, and
|
|
34380
|
+
* `provider_profiles` is not itself a legacy signal);
|
|
34381
|
+
* - an authored `provider_options` is refused with a warning rather than
|
|
34382
|
+
* written while runtime mode is active, and its runtime-side counterpart
|
|
34383
|
+
* (`provider.profiles.*.options`) is lifted back out on import.
|
|
34384
|
+
* Installs with no runtime.yaml, or an inactive one, keep the legacy behavior
|
|
34385
|
+
* unchanged.
|
|
33207
34386
|
*/
|
|
33208
34387
|
var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
33209
|
-
|
|
34388
|
+
/**
|
|
34389
|
+
* The sibling `runtime.yaml` as read from the same scope, or `""` when the
|
|
34390
|
+
* install has none. Import needs it — `toRulesyncPermissions()` is
|
|
34391
|
+
* synchronous, so the file is read alongside config.yaml in `fromFile()`.
|
|
34392
|
+
*/
|
|
34393
|
+
runtimeFileContent;
|
|
34394
|
+
constructor({ runtimeFileContent, ...params }) {
|
|
33210
34395
|
super({
|
|
33211
34396
|
...params,
|
|
33212
34397
|
fileContent: params.fileContent ?? ""
|
|
33213
34398
|
});
|
|
34399
|
+
this.runtimeFileContent = runtimeFileContent ?? "";
|
|
33214
34400
|
}
|
|
33215
34401
|
isDeletable() {
|
|
33216
34402
|
return false;
|
|
@@ -33224,11 +34410,13 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
33224
34410
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
33225
34411
|
const paths = TaktPermissions.getSettablePaths({ global });
|
|
33226
34412
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
34413
|
+
const runtimeFileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, "runtime.yaml")) ?? "";
|
|
33227
34414
|
return new TaktPermissions({
|
|
33228
34415
|
outputRoot,
|
|
33229
34416
|
relativeDirPath: paths.relativeDirPath,
|
|
33230
34417
|
relativeFilePath: paths.relativeFilePath,
|
|
33231
34418
|
fileContent,
|
|
34419
|
+
runtimeFileContent,
|
|
33232
34420
|
validate,
|
|
33233
34421
|
global
|
|
33234
34422
|
});
|
|
@@ -33243,12 +34431,22 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
33243
34431
|
filePath,
|
|
33244
34432
|
invalidRootPolicy: "error"
|
|
33245
34433
|
});
|
|
34434
|
+
const runtime = await readTaktRuntimeConfig({
|
|
34435
|
+
outputRoot,
|
|
34436
|
+
logger
|
|
34437
|
+
});
|
|
33246
34438
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
33247
|
-
const provider = resolveActiveProvider(
|
|
34439
|
+
const provider = resolveActiveProvider({
|
|
34440
|
+
config,
|
|
34441
|
+
runtime: runtime.active ? runtime.config : void 0
|
|
34442
|
+
});
|
|
33248
34443
|
const mode = deriveTaktPermissionMode(rulesyncJson);
|
|
33249
34444
|
const override = isPlainObject$1(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
|
|
33250
34445
|
const stepOverrides = isPlainObject$1(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
33251
|
-
const
|
|
34446
|
+
const authoredProviderOptions = isPlainObject$1(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
34447
|
+
const runtimeModeRefusesProviderOptions = authoredProviderOptions !== void 0 && runtime.active;
|
|
34448
|
+
if (runtimeModeRefusesProviderOptions) logger?.warn(`Takt permissions: not writing "${TAKT_PROVIDER_OPTIONS_KEY}" to ${filePath} because ${runtime.filePaths.join(" + ")} puts Takt in runtime provider mode (Takt 0.56.0+), where any legacy provider setting in config.yaml makes Takt fail with "Mixed provider configuration detected". Move those options to \`provider.profiles.<profile>.options\` in runtime.yaml, and remove them from the \`takt\` block of the rulesync source. A "${TAKT_PROVIDER_OPTIONS_KEY}" already in ${filePath} is left untouched — rulesync does not own that key.`);
|
|
34449
|
+
const overrideProviderOptions = runtimeModeRefusesProviderOptions ? void 0 : authoredProviderOptions;
|
|
33252
34450
|
const authoredPolicies = pickSecurityPolicies(override, {
|
|
33253
34451
|
filePath,
|
|
33254
34452
|
logger,
|
|
@@ -33294,13 +34492,26 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
33294
34492
|
filePath: join(this.getRelativeDirPath(), this.getRelativeFilePath()),
|
|
33295
34493
|
invalidRootPolicy: "error"
|
|
33296
34494
|
});
|
|
33297
|
-
const
|
|
34495
|
+
const runtimeFilePath = join(this.getRelativeDirPath(), TAKT_RUNTIME_CONFIG_FILE_NAME);
|
|
34496
|
+
const runtime = parseSharedConfig({
|
|
34497
|
+
format: "yaml",
|
|
34498
|
+
fileContent: this.runtimeFileContent,
|
|
34499
|
+
filePath: runtimeFilePath
|
|
34500
|
+
});
|
|
34501
|
+
const runtimeActive = isRuntimeModeActive(runtime);
|
|
34502
|
+
const provider = resolveActiveProvider({
|
|
34503
|
+
config,
|
|
34504
|
+
runtime: runtimeActive ? runtime : void 0
|
|
34505
|
+
});
|
|
33298
34506
|
const profiles = isPlainObject$1(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
|
|
33299
34507
|
const profile = isPlainObject$1(profiles[provider]) ? profiles[provider] : {};
|
|
33300
34508
|
const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
|
|
33301
34509
|
const rulesyncConfig = taktModeToRulesyncConfig(mode);
|
|
33302
34510
|
const stepOverrides = isPlainObject$1(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
33303
|
-
const providerOptions =
|
|
34511
|
+
const providerOptions = mergeSharedConfigDeep({
|
|
34512
|
+
base: isPlainObject$1(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : {},
|
|
34513
|
+
patch: runtimeActive ? collectRuntimeProviderOptions(runtime) : {}
|
|
34514
|
+
});
|
|
33304
34515
|
const taktOverride = {};
|
|
33305
34516
|
if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
|
|
33306
34517
|
if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
|
|
@@ -33327,10 +34538,18 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
33327
34538
|
}
|
|
33328
34539
|
};
|
|
33329
34540
|
/**
|
|
33330
|
-
* Resolve the active Takt provider
|
|
33331
|
-
*
|
|
34541
|
+
* Resolve the active Takt provider.
|
|
34542
|
+
*
|
|
34543
|
+
* Under runtime mode (Takt 0.56.0+) the real provider assignment lives in
|
|
34544
|
+
* `runtime.yaml`, and `config.yaml:provider` cannot coexist with it, so the
|
|
34545
|
+
* runtime document is consulted first: the provider of the profile named by
|
|
34546
|
+
* `provider.defaults.profile`. Everything else falls through to the legacy
|
|
34547
|
+
* chain — the top-level `provider:` value, else the sole key in
|
|
34548
|
+
* `provider_profiles`, else the `claude` default.
|
|
33332
34549
|
*/
|
|
33333
|
-
function resolveActiveProvider(config) {
|
|
34550
|
+
function resolveActiveProvider({ config, runtime }) {
|
|
34551
|
+
const fromRuntime = runtime === void 0 ? void 0 : resolveRuntimeProvider(runtime);
|
|
34552
|
+
if (fromRuntime !== void 0) return fromRuntime;
|
|
33334
34553
|
if (typeof config[TAKT_PROVIDER_KEY] === "string" && config[TAKT_PROVIDER_KEY].trim() !== "") return config[TAKT_PROVIDER_KEY];
|
|
33335
34554
|
const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
|
|
33336
34555
|
if (isPlainObject$1(profiles)) {
|
|
@@ -33339,6 +34558,170 @@ function resolveActiveProvider(config) {
|
|
|
33339
34558
|
}
|
|
33340
34559
|
return TAKT_DEFAULT_PROVIDER;
|
|
33341
34560
|
}
|
|
34561
|
+
/** A plain object carrying at least one entry (Takt's "assignment" test). */
|
|
34562
|
+
function hasEntries(value) {
|
|
34563
|
+
return isPlainObject$1(value) && Object.keys(value).length > 0;
|
|
34564
|
+
}
|
|
34565
|
+
function runtimeProfiles(runtime) {
|
|
34566
|
+
const provider = runtime[TAKT_RUNTIME_PROVIDER_KEY];
|
|
34567
|
+
if (!isPlainObject$1(provider)) return {};
|
|
34568
|
+
const profiles = provider[TAKT_RUNTIME_PROFILES_KEY];
|
|
34569
|
+
return isPlainObject$1(profiles) ? profiles : {};
|
|
34570
|
+
}
|
|
34571
|
+
/**
|
|
34572
|
+
* Whether a parsed `runtime.yaml` puts Takt into runtime mode, mirroring
|
|
34573
|
+
* upstream's mode detection: the `provider:` section must carry an actual
|
|
34574
|
+
* assignment — a non-empty `defaults`, `profiles` or `auto_routing`, or a
|
|
34575
|
+
* `targets` map with at least one non-empty nested map. The file existing is not
|
|
34576
|
+
* enough, and empty nested maps (`defaults: {}`, `targets: { personas: {} }`)
|
|
34577
|
+
* must not flip the mode.
|
|
34578
|
+
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/mode.ts
|
|
34579
|
+
*/
|
|
34580
|
+
function isRuntimeModeActive(runtime) {
|
|
34581
|
+
const provider = runtime[TAKT_RUNTIME_PROVIDER_KEY];
|
|
34582
|
+
if (!isPlainObject$1(provider)) return false;
|
|
34583
|
+
if (hasEntries(provider[TAKT_RUNTIME_DEFAULTS_KEY]) || hasEntries(provider[TAKT_RUNTIME_PROFILES_KEY]) || hasEntries(provider[TAKT_RUNTIME_AUTO_ROUTING_KEY])) return true;
|
|
34584
|
+
const targets = provider[TAKT_RUNTIME_TARGETS_KEY];
|
|
34585
|
+
return isPlainObject$1(targets) && Object.values(targets).some(hasEntries);
|
|
34586
|
+
}
|
|
34587
|
+
/** The provider named by the runtime profile Takt would use by default. */
|
|
34588
|
+
function resolveRuntimeProvider(runtime) {
|
|
34589
|
+
const provider = runtime[TAKT_RUNTIME_PROVIDER_KEY];
|
|
34590
|
+
const profiles = runtimeProfiles(runtime);
|
|
34591
|
+
const providerOf = (profileName) => {
|
|
34592
|
+
if (profileName === void 0) return;
|
|
34593
|
+
const profile = profiles[profileName];
|
|
34594
|
+
if (!isPlainObject$1(profile)) return;
|
|
34595
|
+
const value = profile[TAKT_RUNTIME_PROVIDER_KEY];
|
|
34596
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
34597
|
+
};
|
|
34598
|
+
if (!isPlainObject$1(provider)) return;
|
|
34599
|
+
const defaults = provider[TAKT_RUNTIME_DEFAULTS_KEY];
|
|
34600
|
+
if (!isPlainObject$1(defaults) || typeof defaults[TAKT_RUNTIME_PROFILE_KEY] !== "string") return;
|
|
34601
|
+
return providerOf(defaults[TAKT_RUNTIME_PROFILE_KEY]);
|
|
34602
|
+
}
|
|
34603
|
+
/**
|
|
34604
|
+
* Lift the runtime profiles' flat `options` bags back into the per-provider
|
|
34605
|
+
* shape the `takt` override uses. Each profile's options belong to that
|
|
34606
|
+
* profile's own provider; when several profiles name the same provider they are
|
|
34607
|
+
* merged in document order, so a later profile wins on a colliding option key.
|
|
34608
|
+
*/
|
|
34609
|
+
function collectRuntimeProviderOptions(runtime) {
|
|
34610
|
+
const collected = {};
|
|
34611
|
+
for (const profile of Object.values(runtimeProfiles(runtime))) {
|
|
34612
|
+
if (!isPlainObject$1(profile)) continue;
|
|
34613
|
+
const providerName = profile[TAKT_RUNTIME_PROVIDER_KEY];
|
|
34614
|
+
const options = profile[TAKT_RUNTIME_OPTIONS_KEY];
|
|
34615
|
+
if (typeof providerName !== "string" || providerName.trim() === "" || !hasEntries(options)) continue;
|
|
34616
|
+
const existing = collected[providerName];
|
|
34617
|
+
collected[providerName] = {
|
|
34618
|
+
...isPlainObject$1(existing) ? existing : {},
|
|
34619
|
+
...options
|
|
34620
|
+
};
|
|
34621
|
+
}
|
|
34622
|
+
return collected;
|
|
34623
|
+
}
|
|
34624
|
+
/**
|
|
34625
|
+
* Read one `runtime.yaml`. A file that cannot be parsed reports `unparsable`
|
|
34626
|
+
* rather than a document: Takt refuses to start on a broken runtime.yaml, and
|
|
34627
|
+
* treating it as legacy would be the one outcome that writes the
|
|
34628
|
+
* mixed-configuration key into the user's config.yaml.
|
|
34629
|
+
*/
|
|
34630
|
+
async function readTaktRuntimeFile({ filePath, logger }) {
|
|
34631
|
+
const fileContent = await readFileContentOrNull(filePath);
|
|
34632
|
+
if (fileContent === null) return;
|
|
34633
|
+
try {
|
|
34634
|
+
return {
|
|
34635
|
+
config: parseSharedConfig({
|
|
34636
|
+
format: "yaml",
|
|
34637
|
+
fileContent,
|
|
34638
|
+
filePath
|
|
34639
|
+
}),
|
|
34640
|
+
unparsable: false
|
|
34641
|
+
};
|
|
34642
|
+
} catch (error) {
|
|
34643
|
+
logger?.warn(`Takt permissions: could not parse ${filePath} (${formatError(error)}); assuming Takt's runtime provider mode is active so no legacy provider setting is written to config.yaml.`);
|
|
34644
|
+
return {
|
|
34645
|
+
config: {},
|
|
34646
|
+
unparsable: true
|
|
34647
|
+
};
|
|
34648
|
+
}
|
|
34649
|
+
}
|
|
34650
|
+
/**
|
|
34651
|
+
* Collapse the project and global `runtime.yaml` into the single document Takt
|
|
34652
|
+
* itself resolves against, matching upstream's loader: `profiles` is a union
|
|
34653
|
+
* with the project definition of a same-named profile replacing the global one,
|
|
34654
|
+
* while `defaults`, `targets` and `auto_routing` are taken from the project file
|
|
34655
|
+
* whole whenever it states them at all (`??`, so a project `targets: {}` masks
|
|
34656
|
+
* the global one rather than merging with it).
|
|
34657
|
+
* https://github.com/nrslib/takt/blob/main/src/infra/config/runtime-provider/loader.ts
|
|
34658
|
+
*
|
|
34659
|
+
* Merging before mode detection matters in both directions: a project file
|
|
34660
|
+
* active only through `targets:` still resolves its provider from the global
|
|
34661
|
+
* file's `defaults`/`profiles`, and a project section that masks the global one
|
|
34662
|
+
* leaves the merged document inactive even though the global file alone was not.
|
|
34663
|
+
*/
|
|
34664
|
+
function mergeTaktRuntimeConfigs({ project, global: globalConfig }) {
|
|
34665
|
+
const sectionOf = (config) => {
|
|
34666
|
+
const provider = config?.[TAKT_RUNTIME_PROVIDER_KEY];
|
|
34667
|
+
return isPlainObject$1(provider) ? provider : {};
|
|
34668
|
+
};
|
|
34669
|
+
const projectSection = sectionOf(project);
|
|
34670
|
+
const globalSection = sectionOf(globalConfig);
|
|
34671
|
+
const replaced = (key) => projectSection[key] ?? globalSection[key];
|
|
34672
|
+
const profilesOf = (section) => isPlainObject$1(section[TAKT_RUNTIME_PROFILES_KEY]) ? section[TAKT_RUNTIME_PROFILES_KEY] : void 0;
|
|
34673
|
+
const globalProfiles = profilesOf(globalSection);
|
|
34674
|
+
const projectProfiles = profilesOf(projectSection);
|
|
34675
|
+
const provider = {};
|
|
34676
|
+
if (globalProfiles !== void 0 || projectProfiles !== void 0) provider[TAKT_RUNTIME_PROFILES_KEY] = {
|
|
34677
|
+
...globalProfiles,
|
|
34678
|
+
...projectProfiles
|
|
34679
|
+
};
|
|
34680
|
+
for (const key of [
|
|
34681
|
+
TAKT_RUNTIME_DEFAULTS_KEY,
|
|
34682
|
+
TAKT_RUNTIME_TARGETS_KEY,
|
|
34683
|
+
TAKT_RUNTIME_AUTO_ROUTING_KEY
|
|
34684
|
+
]) {
|
|
34685
|
+
const value = replaced(key);
|
|
34686
|
+
if (value !== void 0) provider[key] = value;
|
|
34687
|
+
}
|
|
34688
|
+
return { [TAKT_RUNTIME_PROVIDER_KEY]: provider };
|
|
34689
|
+
}
|
|
34690
|
+
/**
|
|
34691
|
+
* The runtime provider configuration in force for a generate run: the project
|
|
34692
|
+
* and global `runtime.yaml` merged the way Takt merges them, plus whether the
|
|
34693
|
+
* result puts Takt in runtime mode.
|
|
34694
|
+
*
|
|
34695
|
+
* Both scopes are read whichever scope is being generated: Takt collects legacy
|
|
34696
|
+
* signals from the project and global `config.yaml` alike, so a global
|
|
34697
|
+
* `runtime.yaml` — the one Takt generates on first launch — makes a legacy key
|
|
34698
|
+
* in the project config.yaml a hard failure just the same.
|
|
34699
|
+
*/
|
|
34700
|
+
async function readTaktRuntimeConfig({ outputRoot, logger }) {
|
|
34701
|
+
const projectPath = join(outputRoot, TAKT_DIR, TAKT_RUNTIME_CONFIG_FILE_NAME);
|
|
34702
|
+
let globalPath;
|
|
34703
|
+
try {
|
|
34704
|
+
const resolved = join(getHomeDirectory(), TAKT_DIR, TAKT_RUNTIME_CONFIG_FILE_NAME);
|
|
34705
|
+
globalPath = resolved === projectPath ? void 0 : resolved;
|
|
34706
|
+
} catch {}
|
|
34707
|
+
const [project, globalFile] = await Promise.all([readTaktRuntimeFile({
|
|
34708
|
+
filePath: projectPath,
|
|
34709
|
+
logger
|
|
34710
|
+
}), globalPath === void 0 ? Promise.resolve(void 0) : readTaktRuntimeFile({
|
|
34711
|
+
filePath: globalPath,
|
|
34712
|
+
logger
|
|
34713
|
+
})]);
|
|
34714
|
+
const filePaths = [...project === void 0 ? [] : [projectPath], ...globalFile === void 0 || globalPath === void 0 ? [] : [globalPath]];
|
|
34715
|
+
const config = mergeTaktRuntimeConfigs({
|
|
34716
|
+
project: project?.config,
|
|
34717
|
+
global: globalFile?.config
|
|
34718
|
+
});
|
|
34719
|
+
return {
|
|
34720
|
+
filePaths,
|
|
34721
|
+
config,
|
|
34722
|
+
active: project?.unparsable === true || globalFile?.unparsable === true || isRuntimeModeActive(config)
|
|
34723
|
+
};
|
|
34724
|
+
}
|
|
33342
34725
|
/**
|
|
33343
34726
|
* Collapse a rulesync permissions config into Takt's single coarse mode.
|
|
33344
34727
|
*
|
|
@@ -34198,6 +35581,57 @@ function asRecord(value) {
|
|
|
34198
35581
|
return Object.fromEntries(Object.entries(value));
|
|
34199
35582
|
}
|
|
34200
35583
|
/**
|
|
35584
|
+
* The `agent` keys the `zed` override may author. Everything else — above all
|
|
35585
|
+
* `tool_permissions`, which the canonical `permission` block owns end to end —
|
|
35586
|
+
* is refused, so an override can never reach past its own surface and weaken a
|
|
35587
|
+
* canonical deny. The override object is read key by key rather than spread, so
|
|
35588
|
+
* an unlisted key is inert whether or not it is named here — an allowlist, not a
|
|
35589
|
+
* denylist, because `agent` carries blunt instruments of its own (Zed's
|
|
35590
|
+
* `always_allow_tool_actions` would disarm every permission rule at once), and a
|
|
35591
|
+
* verbatim `agent` merge would hand them to the override.
|
|
35592
|
+
*
|
|
35593
|
+
* Every key the patch does not consume is reported, so an unsupported or
|
|
35594
|
+
* misspelled one surfaces as a warning rather than as config that quietly does
|
|
35595
|
+
* nothing. `permission` is exempt: it is the canonical tool-scoped block, and
|
|
35596
|
+
* `RulesyncPermissions.forTarget` has already consumed and stripped it.
|
|
35597
|
+
*/
|
|
35598
|
+
const ZED_OVERRIDE_AGENT_KEYS = ["sandbox_permissions", "profiles"];
|
|
35599
|
+
const ZED_CANONICAL_AGENT_KEY = "tool_permissions";
|
|
35600
|
+
const ZED_OVERRIDE_CONSUMED_KEYS = /* @__PURE__ */ new Set([...ZED_OVERRIDE_AGENT_KEYS, "permission"]);
|
|
35601
|
+
/**
|
|
35602
|
+
* Build the `agent` patch fragment carrying the `zed` override's verbatim
|
|
35603
|
+
* blocks. A key the override supplies replaces the existing block wholesale
|
|
35604
|
+
* (Zed reads each as one unit — a deep merge would leave half of a rewritten
|
|
35605
|
+
* sandbox policy behind); a key it omits is left out of the fragment, so the
|
|
35606
|
+
* existing value survives via the caller's spread of `agent`.
|
|
35607
|
+
*/
|
|
35608
|
+
function buildZedOverridePatch({ override, logger }) {
|
|
35609
|
+
if (!isPlainObject$1(override)) return {};
|
|
35610
|
+
if (ZED_CANONICAL_AGENT_KEY in override) logger?.warn(`Zed permissions: ignoring the 'zed.${ZED_CANONICAL_AGENT_KEY}' override; \`agent.${ZED_CANONICAL_AGENT_KEY}\` is driven by the canonical permission block.`);
|
|
35611
|
+
const unsupportedKeys = Object.keys(override).filter((key) => key !== ZED_CANONICAL_AGENT_KEY && !ZED_OVERRIDE_CONSUMED_KEYS.has(key));
|
|
35612
|
+
if (unsupportedKeys.length > 0) logger?.warn(`Zed permissions: ignoring the ${unsupportedKeys.map((key) => `'zed.${key}'`).join(", ")} override ${unsupportedKeys.length === 1 ? "key" : "keys"} — the \`zed\` block authors only ${ZED_OVERRIDE_AGENT_KEYS.map((key) => `\`${key}\``).join(" and ")}.`);
|
|
35613
|
+
const patch = {};
|
|
35614
|
+
for (const key of ZED_OVERRIDE_AGENT_KEYS) {
|
|
35615
|
+
const value = override[key];
|
|
35616
|
+
if (isPlainObject$1(value)) patch[key] = value;
|
|
35617
|
+
}
|
|
35618
|
+
return patch;
|
|
35619
|
+
}
|
|
35620
|
+
/**
|
|
35621
|
+
* The write-side inverse: lift `agent.sandbox_permissions` / `agent.profiles`
|
|
35622
|
+
* back into the `zed` override so a hand-written sandbox policy or profile set
|
|
35623
|
+
* round-trips instead of being lost on the next generate. Returns `undefined`
|
|
35624
|
+
* when the settings carry neither, so the override key is omitted.
|
|
35625
|
+
*/
|
|
35626
|
+
function extractZedOverride(agent) {
|
|
35627
|
+
const override = {};
|
|
35628
|
+
for (const key of ZED_OVERRIDE_AGENT_KEYS) {
|
|
35629
|
+
const value = agent[key];
|
|
35630
|
+
if (isPlainObject$1(value)) override[key] = value;
|
|
35631
|
+
}
|
|
35632
|
+
return Object.keys(override).length > 0 ? override : void 0;
|
|
35633
|
+
}
|
|
35634
|
+
/**
|
|
34201
35635
|
* Permissions generator for the Zed editor.
|
|
34202
35636
|
*
|
|
34203
35637
|
* Zed maps tool permissions onto `agent.tool_permissions` inside its settings
|
|
@@ -34205,6 +35639,11 @@ function asRecord(value) {
|
|
|
34205
35639
|
* global). That file is shared with the MCP (`context_servers`) and ignore
|
|
34206
35640
|
* (`private_files`) features, so reads and writes merge into the existing JSON
|
|
34207
35641
|
* rather than overwriting it, and the file is never deleted.
|
|
35642
|
+
*
|
|
35643
|
+
* Zed's OS sandbox (`agent.sandbox_permissions`) and its tool-availability
|
|
35644
|
+
* profiles (`agent.profiles`) are separate enforcement layers with no canonical
|
|
35645
|
+
* counterpart; they are authored verbatim through the `zed` override and lifted
|
|
35646
|
+
* back out of the settings on import. See `ZedPermissionsOverrideSchema`.
|
|
34208
35647
|
*/
|
|
34209
35648
|
var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
34210
35649
|
constructor(params) {
|
|
@@ -34283,6 +35722,10 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
34283
35722
|
existingContent,
|
|
34284
35723
|
patch: { agent: {
|
|
34285
35724
|
...agent,
|
|
35725
|
+
...buildZedOverridePatch({
|
|
35726
|
+
override: config.zed,
|
|
35727
|
+
logger
|
|
35728
|
+
}),
|
|
34286
35729
|
tool_permissions: {
|
|
34287
35730
|
...toolPermissions,
|
|
34288
35731
|
...managedDefault !== void 0 && { default: managedDefault },
|
|
@@ -34304,7 +35747,8 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
34304
35747
|
} catch (error) {
|
|
34305
35748
|
throw new Error(`Failed to parse Zed permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
34306
35749
|
}
|
|
34307
|
-
const
|
|
35750
|
+
const agent = asRecord(settings.agent);
|
|
35751
|
+
const toolPermissionsRaw = agent.tool_permissions;
|
|
34308
35752
|
const parsed = ZedToolPermissionsSchema.safeParse(toolPermissionsRaw ?? {});
|
|
34309
35753
|
const tools = parsed.success ? parsed.data.tools ?? {} : {};
|
|
34310
35754
|
const globalDefault = parsed.success ? parsed.data.default : void 0;
|
|
@@ -34328,7 +35772,10 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
34328
35772
|
for (const entry of toolPermission.always_deny ?? []) ensure(category)[entry.pattern] = "deny";
|
|
34329
35773
|
for (const entry of toolPermission.always_confirm ?? []) ensure(category)[entry.pattern] = "ask";
|
|
34330
35774
|
}
|
|
34331
|
-
|
|
35775
|
+
const zedOverride = extractZedOverride(agent);
|
|
35776
|
+
const result = { permission };
|
|
35777
|
+
if (zedOverride !== void 0) result.zed = zedOverride;
|
|
35778
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
34332
35779
|
}
|
|
34333
35780
|
validate() {
|
|
34334
35781
|
return {
|
|
@@ -34414,6 +35861,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
34414
35861
|
supportsImport: true
|
|
34415
35862
|
}
|
|
34416
35863
|
}],
|
|
35864
|
+
["copilotcli", {
|
|
35865
|
+
class: CopilotcliPermissions,
|
|
35866
|
+
meta: {
|
|
35867
|
+
supportsProject: true,
|
|
35868
|
+
supportsGlobal: true,
|
|
35869
|
+
supportsImport: true
|
|
35870
|
+
}
|
|
35871
|
+
}],
|
|
34417
35872
|
["cursor", {
|
|
34418
35873
|
class: CursorPermissions,
|
|
34419
35874
|
meta: {
|
|
@@ -36367,7 +37822,8 @@ function openaiYamlToCodexcliSection(parsed) {
|
|
|
36367
37822
|
/**
|
|
36368
37823
|
* Represents a Codex CLI skill directory.
|
|
36369
37824
|
* Codex CLI supports skills in both project mode (under $CWD/.agents/skills)
|
|
36370
|
-
* and global mode (under
|
|
37825
|
+
* and global mode (under ~/.agents/skills — Rulesync resolves the home
|
|
37826
|
+
* directory rather than $CODEX_HOME).
|
|
36371
37827
|
*/
|
|
36372
37828
|
var CodexCliSkill = class CodexCliSkill extends ToolSkill {
|
|
36373
37829
|
constructor({ outputRoot = process.cwd(), relativeDirPath = CODEXCLI_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -38403,6 +39859,145 @@ var KiroIdeSkill = class extends KiroSkill {
|
|
|
38403
39859
|
}
|
|
38404
39860
|
};
|
|
38405
39861
|
//#endregion
|
|
39862
|
+
//#region src/features/skills/musecode-skill.ts
|
|
39863
|
+
const MusecodeSkillFrontmatterSchema = z.looseObject({
|
|
39864
|
+
name: z.string(),
|
|
39865
|
+
description: z.string()
|
|
39866
|
+
});
|
|
39867
|
+
/**
|
|
39868
|
+
* Represents a Meta Muse Code skill directory.
|
|
39869
|
+
*
|
|
39870
|
+
* Muse Code reads Agent Skills as `<skill-id>/SKILL.md` directories from the
|
|
39871
|
+
* project (`.agents/skills/`) and from the user config
|
|
39872
|
+
* (`$XDG_CONFIG_HOME/muse/skills`, plus `~/.agents/skills`). rulesync emits the
|
|
39873
|
+
* project directory and, at global scope, only the XDG-default
|
|
39874
|
+
* `~/.config/muse/skills` so a skill is written exactly once. Muse Code's
|
|
39875
|
+
* compat scans of repo-local `.codex/skills` and `.claude/skills` belong to
|
|
39876
|
+
* other tools and are not emitted.
|
|
39877
|
+
* @see https://dev.meta.ai/docs/muse-code/extending.md
|
|
39878
|
+
*/
|
|
39879
|
+
var MusecodeSkill = class MusecodeSkill extends ToolSkill {
|
|
39880
|
+
constructor({ outputRoot = process.cwd(), relativeDirPath = MUSECODE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
39881
|
+
super({
|
|
39882
|
+
outputRoot,
|
|
39883
|
+
relativeDirPath,
|
|
39884
|
+
dirName,
|
|
39885
|
+
mainFile: {
|
|
39886
|
+
name: SKILL_FILE_NAME,
|
|
39887
|
+
body,
|
|
39888
|
+
frontmatter: { ...frontmatter }
|
|
39889
|
+
},
|
|
39890
|
+
otherFiles,
|
|
39891
|
+
global
|
|
39892
|
+
});
|
|
39893
|
+
if (validate) {
|
|
39894
|
+
const result = this.validate();
|
|
39895
|
+
if (!result.success) throw result.error;
|
|
39896
|
+
}
|
|
39897
|
+
}
|
|
39898
|
+
static getSettablePaths({ global = false } = {}) {
|
|
39899
|
+
return { relativeDirPath: global ? MUSECODE_GLOBAL_SKILLS_DIR_PATH : MUSECODE_SKILLS_DIR_PATH };
|
|
39900
|
+
}
|
|
39901
|
+
getFrontmatter() {
|
|
39902
|
+
return MusecodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
39903
|
+
}
|
|
39904
|
+
getBody() {
|
|
39905
|
+
return this.mainFile?.body ?? "";
|
|
39906
|
+
}
|
|
39907
|
+
validate() {
|
|
39908
|
+
if (!this.mainFile) return {
|
|
39909
|
+
success: false,
|
|
39910
|
+
error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
|
|
39911
|
+
};
|
|
39912
|
+
const result = MusecodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
|
|
39913
|
+
if (!result.success) return {
|
|
39914
|
+
success: false,
|
|
39915
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
|
|
39916
|
+
};
|
|
39917
|
+
return {
|
|
39918
|
+
success: true,
|
|
39919
|
+
error: null
|
|
39920
|
+
};
|
|
39921
|
+
}
|
|
39922
|
+
toRulesyncSkill() {
|
|
39923
|
+
const frontmatter = this.getFrontmatter();
|
|
39924
|
+
const rulesyncFrontmatter = {
|
|
39925
|
+
name: frontmatter.name,
|
|
39926
|
+
description: frontmatter.description,
|
|
39927
|
+
targets: ["*"]
|
|
39928
|
+
};
|
|
39929
|
+
return new RulesyncSkill({
|
|
39930
|
+
outputRoot: this.outputRoot,
|
|
39931
|
+
relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
|
|
39932
|
+
dirName: this.getDirName(),
|
|
39933
|
+
frontmatter: rulesyncFrontmatter,
|
|
39934
|
+
body: this.getBody(),
|
|
39935
|
+
otherFiles: this.getOtherFiles(),
|
|
39936
|
+
validate: true,
|
|
39937
|
+
global: this.global
|
|
39938
|
+
});
|
|
39939
|
+
}
|
|
39940
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
39941
|
+
const settablePaths = MusecodeSkill.getSettablePaths({ global });
|
|
39942
|
+
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
39943
|
+
const musecodeFrontmatter = {
|
|
39944
|
+
name: rulesyncFrontmatter.name,
|
|
39945
|
+
description: rulesyncFrontmatter.description
|
|
39946
|
+
};
|
|
39947
|
+
return new MusecodeSkill({
|
|
39948
|
+
outputRoot,
|
|
39949
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
39950
|
+
dirName: rulesyncSkill.getDirName(),
|
|
39951
|
+
frontmatter: musecodeFrontmatter,
|
|
39952
|
+
body: rulesyncSkill.getBody(),
|
|
39953
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
39954
|
+
validate,
|
|
39955
|
+
global
|
|
39956
|
+
});
|
|
39957
|
+
}
|
|
39958
|
+
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
39959
|
+
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
39960
|
+
return targets.includes("*") || targets.includes("musecode");
|
|
39961
|
+
}
|
|
39962
|
+
static async fromDir(params) {
|
|
39963
|
+
const loaded = await this.loadSkillDirContent({
|
|
39964
|
+
...params,
|
|
39965
|
+
getSettablePaths: MusecodeSkill.getSettablePaths
|
|
39966
|
+
});
|
|
39967
|
+
const result = MusecodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
|
|
39968
|
+
if (!result.success) {
|
|
39969
|
+
const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
39970
|
+
throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
39971
|
+
}
|
|
39972
|
+
return new MusecodeSkill({
|
|
39973
|
+
outputRoot: loaded.outputRoot,
|
|
39974
|
+
relativeDirPath: loaded.relativeDirPath,
|
|
39975
|
+
dirName: loaded.dirName,
|
|
39976
|
+
frontmatter: result.data,
|
|
39977
|
+
body: loaded.body,
|
|
39978
|
+
otherFiles: loaded.otherFiles,
|
|
39979
|
+
validate: true,
|
|
39980
|
+
global: loaded.global
|
|
39981
|
+
});
|
|
39982
|
+
}
|
|
39983
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
|
|
39984
|
+
const settablePaths = MusecodeSkill.getSettablePaths({ global });
|
|
39985
|
+
return new MusecodeSkill({
|
|
39986
|
+
outputRoot,
|
|
39987
|
+
relativeDirPath: relativeDirPath ?? settablePaths.relativeDirPath,
|
|
39988
|
+
dirName,
|
|
39989
|
+
frontmatter: {
|
|
39990
|
+
name: "",
|
|
39991
|
+
description: ""
|
|
39992
|
+
},
|
|
39993
|
+
body: "",
|
|
39994
|
+
otherFiles: [],
|
|
39995
|
+
validate: false,
|
|
39996
|
+
global
|
|
39997
|
+
});
|
|
39998
|
+
}
|
|
39999
|
+
};
|
|
40000
|
+
//#endregion
|
|
38406
40001
|
//#region src/features/skills/opencode-skill.ts
|
|
38407
40002
|
const OpenCodeSkillFrontmatterSchema = z.looseObject({
|
|
38408
40003
|
name: z.string(),
|
|
@@ -40231,6 +41826,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
40231
41826
|
supportsGlobal: true
|
|
40232
41827
|
}
|
|
40233
41828
|
}],
|
|
41829
|
+
["musecode", {
|
|
41830
|
+
class: MusecodeSkill,
|
|
41831
|
+
meta: {
|
|
41832
|
+
supportsProject: true,
|
|
41833
|
+
supportsSimulated: false,
|
|
41834
|
+
supportsGlobal: true
|
|
41835
|
+
}
|
|
41836
|
+
}],
|
|
40234
41837
|
["opencode", {
|
|
40235
41838
|
class: OpenCodeSkill,
|
|
40236
41839
|
meta: {
|
|
@@ -42751,7 +44354,7 @@ var FactorydroidSubagent = class FactorydroidSubagent extends ToolSubagent {
|
|
|
42751
44354
|
* `sub_recipes` list, which rulesync never wrote) — so those files were inert.
|
|
42752
44355
|
* The custom-agent surface is the one Goose actually reads.
|
|
42753
44356
|
*
|
|
42754
|
-
* @see https://
|
|
44357
|
+
* @see https://goose-docs.ai/docs/guides/context-engineering/custom-agents/
|
|
42755
44358
|
*/
|
|
42756
44359
|
const GooseSubagentFrontmatterSchema = z.looseObject({
|
|
42757
44360
|
name: z.string(),
|
|
@@ -48085,7 +49688,7 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
48085
49688
|
* touches during a session. The separate `.goose/memories/` tree is the Memory
|
|
48086
49689
|
* extension's storage and is NOT auto-loaded as session context.
|
|
48087
49690
|
* (Verified against the official docs:
|
|
48088
|
-
* https://
|
|
49691
|
+
* https://goose-docs.ai/docs/guides/context-engineering/using-goosehints/)
|
|
48089
49692
|
*
|
|
48090
49693
|
* rulesync's topic-based non-root rules have no project subdirectory to map onto,
|
|
48091
49694
|
* so writing them under `.goose/memories/` made them effectively invisible to
|
|
@@ -48096,7 +49699,7 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
48096
49699
|
* Goose uses plain markdown files (.goosehints) without frontmatter.
|
|
48097
49700
|
*
|
|
48098
49701
|
* Global scope emits only `~/.config/goose/.goosehints`. Goose v1.41.0 (PR
|
|
48099
|
-
*
|
|
49702
|
+
* aaif-goose/goose#9736) additionally loads the vendor-neutral
|
|
48100
49703
|
* `~/.agents/AGENTS.md` alongside the config-dir hints, but rulesync
|
|
48101
49704
|
* deliberately does not emit that shared path from the goose target: the
|
|
48102
49705
|
* config-dir hints remain fully loaded (no capability loss), and the
|
|
@@ -48804,6 +50407,73 @@ var KiroIdeRule = class extends KiroRule {
|
|
|
48804
50407
|
}
|
|
48805
50408
|
};
|
|
48806
50409
|
//#endregion
|
|
50410
|
+
//#region src/features/rules/musecode-rule.ts
|
|
50411
|
+
var MusecodeRule = class MusecodeRule extends ToolRule {
|
|
50412
|
+
constructor({ fileContent, root, ...rest }) {
|
|
50413
|
+
super({
|
|
50414
|
+
...rest,
|
|
50415
|
+
fileContent,
|
|
50416
|
+
root: root ?? false
|
|
50417
|
+
});
|
|
50418
|
+
}
|
|
50419
|
+
static getSettablePaths(_options = {}) {
|
|
50420
|
+
return { root: {
|
|
50421
|
+
relativeDirPath: ".",
|
|
50422
|
+
relativeFilePath: MUSECODE_RULE_FILE_NAME
|
|
50423
|
+
} };
|
|
50424
|
+
}
|
|
50425
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
50426
|
+
const { root } = this.getSettablePaths({ global });
|
|
50427
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
50428
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
50429
|
+
return new MusecodeRule({
|
|
50430
|
+
outputRoot,
|
|
50431
|
+
relativeDirPath: root.relativeDirPath,
|
|
50432
|
+
relativeFilePath: root.relativeFilePath,
|
|
50433
|
+
fileContent,
|
|
50434
|
+
validate,
|
|
50435
|
+
root: true
|
|
50436
|
+
});
|
|
50437
|
+
}
|
|
50438
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
50439
|
+
const { root } = this.getSettablePaths({ global });
|
|
50440
|
+
const isRoot = rulesyncRule.getFrontmatter().root ?? false;
|
|
50441
|
+
return new MusecodeRule({
|
|
50442
|
+
outputRoot,
|
|
50443
|
+
relativeDirPath: root.relativeDirPath,
|
|
50444
|
+
relativeFilePath: root.relativeFilePath,
|
|
50445
|
+
fileContent: rulesyncRule.getBody(),
|
|
50446
|
+
validate,
|
|
50447
|
+
root: isRoot
|
|
50448
|
+
});
|
|
50449
|
+
}
|
|
50450
|
+
toRulesyncRule() {
|
|
50451
|
+
return this.toRulesyncRuleDefault();
|
|
50452
|
+
}
|
|
50453
|
+
validate() {
|
|
50454
|
+
return {
|
|
50455
|
+
success: true,
|
|
50456
|
+
error: null
|
|
50457
|
+
};
|
|
50458
|
+
}
|
|
50459
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
50460
|
+
return new MusecodeRule({
|
|
50461
|
+
outputRoot,
|
|
50462
|
+
relativeDirPath,
|
|
50463
|
+
relativeFilePath,
|
|
50464
|
+
fileContent: "",
|
|
50465
|
+
validate: false,
|
|
50466
|
+
root: relativeFilePath === "AGENTS.md" && relativeDirPath === "."
|
|
50467
|
+
});
|
|
50468
|
+
}
|
|
50469
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
50470
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
50471
|
+
rulesyncRule,
|
|
50472
|
+
toolTarget: "musecode"
|
|
50473
|
+
});
|
|
50474
|
+
}
|
|
50475
|
+
};
|
|
50476
|
+
//#endregion
|
|
48807
50477
|
//#region src/features/rules/opencode-rule.ts
|
|
48808
50478
|
var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
48809
50479
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
@@ -50384,6 +52054,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
50384
52054
|
ruleDiscoveryMode: "toon"
|
|
50385
52055
|
}
|
|
50386
52056
|
}],
|
|
52057
|
+
["musecode", {
|
|
52058
|
+
class: MusecodeRule,
|
|
52059
|
+
meta: {
|
|
52060
|
+
extension: "md",
|
|
52061
|
+
supportsGlobal: false,
|
|
52062
|
+
ruleDiscoveryMode: "auto",
|
|
52063
|
+
collisionPolicy: "fold"
|
|
52064
|
+
}
|
|
52065
|
+
}],
|
|
50387
52066
|
["opencode", {
|
|
50388
52067
|
class: OpenCodeRule,
|
|
50389
52068
|
meta: {
|
|
@@ -53043,4 +54722,4 @@ async function importChecksCore(params) {
|
|
|
53043
54722
|
//#endregion
|
|
53044
54723
|
export { JsonLogger as $, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as $t, RulesyncRuleFrontmatterSchema as A, ALL_TOOL_TARGETS_WITH_WILDCARD as At, RulesyncCheck as B, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Bt, CODEXCLI_DIR as C, removeTempDirectory as Ct, RulesyncSkill as D, writeFileBuffer as Dt, RulesyncSubagentFrontmatterSchema as E, toPosixPath as Et, getRulesyncSourceCandidates as F, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Ft, SKILL_FILE_NAME as G, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Gt, stringifyFrontmatter as H, RULESYNC_HOOKS_FILE_NAME as Ht, resolveRulesyncSourceWritePath as I, RULESYNC_CHECKS_RELATIVE_DIR_PATH as It, ConfigFileSchema as J, RULESYNC_MCP_LEGACY_FILE_NAME as Jt, ConfigResolver as K, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Kt, parseJsonc as L, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Lt, RulesyncMcp as M, ToolTargetSchema as Mt, RulesyncIgnore as N, MAX_FILE_SIZE as Nt, RulesyncSkillFrontmatterSchema as O, writeFileContent as Ot, RulesyncHooks as P, RULESYNC_AIIGNORE_FILE_NAME as Pt, ConsoleLogger as Q, RULESYNC_PERMISSIONS_FILE_NAME as Qt, RulesyncCommand as R, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Rt, CODEXCLI_BASH_RULES_FILE_NAME as S, removeFileStrict as St, RulesyncSubagent as T, runWithDirectoryRollback as Tt, loadYaml as U, RULESYNC_HOOKS_LEGACY_FILE_NAME as Ut, RulesyncCheckFrontmatterSchema as V, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Vt, SHARED_USER_MANAGED_CONFIG_PATHS as W, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Wt, SourceEntrySchema as X, RULESYNC_MCP_SCHEMA_URL as Xt, GITIGNORE_DESTINATION_KEY as Y, RULESYNC_MCP_RELATIVE_FILE_PATH as Yt, findControlCharacter as Z, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Zt, CLAUDECODE_LOCAL_RULE_FILE_NAME as _, readFileContent as _t, convertFromTool as a, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as an, assertTreeContainsNoSymlinks as at, CLAUDECODE_SKILLS_DIR_PATH as b, removeDirectoryStrict as bt, SubagentsProcessor as c, ALL_FEATURES_WITH_WILDCARD as cn, createTempDirectory as ct, IgnoreProcessor as d, fileExists as dt, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as en, fallbackLogger as et, HooksProcessor as f, findFilesByGlobs as ft, CLAUDECODE_DIR as g, listDirectoryFiles as gt, QWENCODE_LOCAL_RULE_FILE_NAME as h, isSymlink as ht, getProcessorRegistryEntry as i, RULESYNC_SKILLS_RELATIVE_DIR_PATH as in, assertDirectoryIfExists as it, RulesyncPermissions as j, PACKAGING_TOOL_TARGETS as jt, RulesyncRule as k, ALL_TOOL_TARGETS as kt, SkillsProcessor as l, DEPRECATED_FEATURE_REPLACEMENTS as ln, directoryExists as lt, QWENCODE_DIR as m, getHomeDirectory as mt, checkRulesyncDirExists as n, RULESYNC_RELATIVE_DIR_PATH as nn, CLIError as nt, isPackagingToolTarget as o, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as on, assertWritablePathInsideRoot as ot, CommandsProcessor as p, getFileSize as pt, CONFLICTING_TARGET_PAIRS as q, RULESYNC_MCP_FILE_NAME as qt, generate as r, RULESYNC_RULES_RELATIVE_DIR_PATH as rn, ErrorCodes as rt, RulesProcessor as s, ALL_FEATURES as sn, checkPathTraversal as st, importFromTool as t, RULESYNC_PERMISSIONS_SCHEMA_URL as tn, warnOnConflictingFlags as tt, McpProcessor as u, formatError as un, ensureDir as ut, CLAUDECODE_MEMORIES_DIR_NAME as v, readFileContentOrNull as vt, getLocalSkillDirNames as w, resolvePath as wt, ChecksProcessor as x, removeFile as xt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as y, removeDirectory as yt, RulesyncCommandFrontmatterSchema as z, RULESYNC_CONFIG_SCHEMA_URL as zt };
|
|
53045
54724
|
|
|
53046
|
-
//# sourceMappingURL=import-
|
|
54725
|
+
//# sourceMappingURL=import-Bgkf_jVN.js.map
|