rulesync 16.36.0 → 16.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.cjs +4 -4
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-D_iukHSy.js → import-BiyWNVZY.js} +757 -90
- package/dist/import-BiyWNVZY.js.map +1 -0
- package/dist/{import-D_daTMgR.cjs → import-DKBaTqsY.cjs} +773 -106
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-D_iukHSy.js.map +0 -1
|
@@ -591,6 +591,7 @@ const mcpProcessorToolTargetTuple = [
|
|
|
591
591
|
"junie",
|
|
592
592
|
"musecode",
|
|
593
593
|
"opencode",
|
|
594
|
+
"pool",
|
|
594
595
|
"qwencode",
|
|
595
596
|
"reasonix",
|
|
596
597
|
"roo",
|
|
@@ -3304,6 +3305,8 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
3304
3305
|
".grok/config.toml",
|
|
3305
3306
|
".rovodev/config.yml",
|
|
3306
3307
|
".rovodev/mcp.json",
|
|
3308
|
+
".poolside/settings.yaml",
|
|
3309
|
+
".config/poolside/settings.yaml",
|
|
3307
3310
|
".tabnine/agent/settings.json",
|
|
3308
3311
|
".vibe/config.toml",
|
|
3309
3312
|
".zcode/config.json",
|
|
@@ -4353,7 +4356,7 @@ const PI_HOOK_EVENTS = [
|
|
|
4353
4356
|
* adapter. Amp's `agent.start` / `agent.end` events describe the main agent
|
|
4354
4357
|
* turn, not a subagent lifecycle, so they map to `beforeSubmitPrompt` / `stop`.
|
|
4355
4358
|
*
|
|
4356
|
-
* @see https://ampcode.com/
|
|
4359
|
+
* @see https://ampcode.com/docs/plugin-api
|
|
4357
4360
|
*/
|
|
4358
4361
|
const AMP_HOOK_EVENTS = [
|
|
4359
4362
|
"sessionStart",
|
|
@@ -6198,6 +6201,7 @@ const McpServerSchema = z.looseObject({
|
|
|
6198
6201
|
crushOauthClientSecret: z.optional(z.string()),
|
|
6199
6202
|
crushOauthCallbackPort: z.optional(z.int()),
|
|
6200
6203
|
crushSessionless: z.optional(z.boolean()),
|
|
6204
|
+
poolAllow: z.optional(z.array(z.string())),
|
|
6201
6205
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
6202
6206
|
/**
|
|
6203
6207
|
* The canonical per-server tool allowlist.
|
|
@@ -6629,6 +6633,7 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
6629
6633
|
"crushOauthClientSecret",
|
|
6630
6634
|
"crushOauthCallbackPort",
|
|
6631
6635
|
"crushSessionless",
|
|
6636
|
+
"poolAllow",
|
|
6632
6637
|
"oauth_client_id",
|
|
6633
6638
|
"oauth_client_secret",
|
|
6634
6639
|
"oauth_callback_port",
|
|
@@ -8316,7 +8321,7 @@ const PERMISSION_OVERRIDE_KEY_ALIASES = {
|
|
|
8316
8321
|
* them; treating a segment that carries one as static could name a directory
|
|
8317
8322
|
* that no matched file actually lives under.
|
|
8318
8323
|
*/
|
|
8319
|
-
const GLOB_METACHARACTERS = /[*?[\]{}()!]/;
|
|
8324
|
+
const GLOB_METACHARACTERS$1 = /[*?[\]{}()!]/;
|
|
8320
8325
|
/**
|
|
8321
8326
|
* The directory every file matched by `glob` lives under, or `undefined` when
|
|
8322
8327
|
* the pattern pins down no such directory.
|
|
@@ -8349,7 +8354,7 @@ function getGlobStaticPrefix(glob) {
|
|
|
8349
8354
|
const lastIndex = segments.length - 1;
|
|
8350
8355
|
const prefix = [];
|
|
8351
8356
|
for (const [index, segment] of segments.entries()) {
|
|
8352
|
-
if (segment === "" || GLOB_METACHARACTERS.test(segment)) break;
|
|
8357
|
+
if (segment === "" || GLOB_METACHARACTERS$1.test(segment)) break;
|
|
8353
8358
|
if (segment === ".") continue;
|
|
8354
8359
|
if (index === lastIndex) break;
|
|
8355
8360
|
prefix.push(segment);
|
|
@@ -8420,6 +8425,7 @@ const RulesyncRuleFrontmatterSchema = z.object({
|
|
|
8420
8425
|
trigger: z.optional(z.string()),
|
|
8421
8426
|
globs: z.optional(z.array(z.string()))
|
|
8422
8427
|
})),
|
|
8428
|
+
aiassistant: z.optional(z.looseObject({ apply: z.optional(z.string()) })),
|
|
8423
8429
|
devin: z.optional(z.looseObject({
|
|
8424
8430
|
trigger: z.optional(z.string()),
|
|
8425
8431
|
globs: z.optional(z.array(z.string())),
|
|
@@ -9644,6 +9650,8 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
9644
9650
|
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
9645
9651
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
9646
9652
|
"user-invocable": z.optional(z.boolean()),
|
|
9653
|
+
icon: z.optional(z.string()),
|
|
9654
|
+
color: z.optional(z.string()),
|
|
9647
9655
|
metadata: z.optional(z.looseObject({}))
|
|
9648
9656
|
})),
|
|
9649
9657
|
factorydroid: z.optional(z.looseObject({
|
|
@@ -12921,6 +12929,8 @@ const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
|
12921
12929
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
12922
12930
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
12923
12931
|
const ROVODEV_CONFIG_SHARED_FILE_KEY = ".rovodev/config.yml";
|
|
12932
|
+
const POOL_PROJECT_SETTINGS_SHARED_FILE_KEY = ".poolside/settings.yaml";
|
|
12933
|
+
const POOL_GLOBAL_SETTINGS_SHARED_FILE_KEY = ".config/poolside/settings.yaml";
|
|
12924
12934
|
/**
|
|
12925
12935
|
* Build the `SHARED_CONFIG_OWNERSHIP` lookup key from a tool's settable paths.
|
|
12926
12936
|
* Mirrors `sharedFileKey` in `src/lib/shared-file-derive.ts` (kept separate so
|
|
@@ -13002,6 +13012,20 @@ const KIMI_CODE_CONFIG_DECLARATION = {
|
|
|
13002
13012
|
}
|
|
13003
13013
|
};
|
|
13004
13014
|
/**
|
|
13015
|
+
* Pool settings (`.poolside/settings.yaml` / `~/.config/poolside/settings.yaml`):
|
|
13016
|
+
* the user's primary Pool settings file, so an unparseable root fails closed
|
|
13017
|
+
* rather than being replaced with generated output. `mcp_servers` is owned as
|
|
13018
|
+
* a whole key because the MCP writer emits the complete server map.
|
|
13019
|
+
*/
|
|
13020
|
+
const POOL_SETTINGS_DECLARATION = {
|
|
13021
|
+
format: "yaml",
|
|
13022
|
+
invalidRootPolicy: "error",
|
|
13023
|
+
features: { mcp: {
|
|
13024
|
+
kind: "replace-owned-keys",
|
|
13025
|
+
ownedKeys: ["mcp_servers"]
|
|
13026
|
+
} }
|
|
13027
|
+
};
|
|
13028
|
+
/**
|
|
13005
13029
|
* ZCode's settings file, which also carries model/theme/permission keys
|
|
13006
13030
|
* rulesync does not own. Both copies are the user's primary ZCode config, so
|
|
13007
13031
|
* every writer refuses to read-modify-write a file it could not parse rather
|
|
@@ -13106,6 +13130,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13106
13130
|
[HERMES_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
13107
13131
|
[HERMES_WIN32_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
13108
13132
|
[HERMES_HOME_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
13133
|
+
[POOL_PROJECT_SETTINGS_SHARED_FILE_KEY]: POOL_SETTINGS_DECLARATION,
|
|
13134
|
+
[POOL_GLOBAL_SETTINGS_SHARED_FILE_KEY]: POOL_SETTINGS_DECLARATION,
|
|
13109
13135
|
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
13110
13136
|
format: "yaml",
|
|
13111
13137
|
invalidRootPolicy: "error",
|
|
@@ -23658,14 +23684,23 @@ var ContinueHooks = class extends SettingsJsonHooks {
|
|
|
23658
23684
|
* portable `command` field is written, which upstream copies to both when
|
|
23659
23685
|
* neither is present. Note the cloud agent runs hooks in a Linux sandbox and
|
|
23660
23686
|
* honors only `bash` and `command` — a `powershell` entry is ignored there.
|
|
23687
|
+
* VS Code reads the same file and maps `bash` to `osx` / `linux` and
|
|
23688
|
+
* `powershell` to `windows`; it also documents its own per-OS overrides
|
|
23689
|
+
* (`windows`, `linux`, `osx`) next to `command`, which are typed here so an
|
|
23690
|
+
* entry that uses them instead of a shell field is not mistaken for one
|
|
23691
|
+
* without a command.
|
|
23661
23692
|
*
|
|
23662
23693
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23694
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23663
23695
|
*/
|
|
23664
23696
|
const CopilotHookEntrySchema = z.looseObject({
|
|
23665
23697
|
type: z.string(),
|
|
23666
23698
|
bash: z.optional(z.string()),
|
|
23667
23699
|
powershell: z.optional(z.string()),
|
|
23668
23700
|
command: z.optional(z.string()),
|
|
23701
|
+
windows: z.optional(z.string()),
|
|
23702
|
+
linux: z.optional(z.string()),
|
|
23703
|
+
osx: z.optional(z.string()),
|
|
23669
23704
|
cwd: z.optional(z.string()),
|
|
23670
23705
|
env: z.optional(z.record(z.string(), z.string())),
|
|
23671
23706
|
timeoutSec: z.optional(z.number()),
|
|
@@ -23756,17 +23791,30 @@ function resolveImportCommand$1(entry, logger) {
|
|
|
23756
23791
|
*
|
|
23757
23792
|
* Generate re-emits any non-canonical key verbatim through `rest`, so a key
|
|
23758
23793
|
* dropped here does not survive an import → generate round trip. `cwd` is a
|
|
23759
|
-
* documented Copilot hook field and was previously lost that way
|
|
23794
|
+
* documented Copilot hook field and was previously lost that way, as were the
|
|
23795
|
+
* VS Code per-OS overrides `windows` / `linux` / `osx`: an entry authored with
|
|
23796
|
+
* only those (no `command`) imported as a hook with no command at all and was
|
|
23797
|
+
* regenerated empty.
|
|
23760
23798
|
*
|
|
23761
23799
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23762
23800
|
* @see https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks
|
|
23801
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23763
23802
|
*/
|
|
23764
23803
|
function importPassthrough$1(entry) {
|
|
23765
23804
|
const passthrough = {};
|
|
23766
23805
|
if (entry.cwd !== void 0) passthrough.cwd = entry.cwd;
|
|
23767
23806
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
23807
|
+
for (const key of OS_OVERRIDE_KEYS) if (entry[key] !== void 0) passthrough[key] = entry[key];
|
|
23768
23808
|
return passthrough;
|
|
23769
23809
|
}
|
|
23810
|
+
const OS_OVERRIDE_KEYS = [
|
|
23811
|
+
"windows",
|
|
23812
|
+
"linux",
|
|
23813
|
+
"osx"
|
|
23814
|
+
];
|
|
23815
|
+
function hasOsOverride(passthrough) {
|
|
23816
|
+
return OS_OVERRIDE_KEYS.some((key) => passthrough[key] !== void 0);
|
|
23817
|
+
}
|
|
23770
23818
|
/**
|
|
23771
23819
|
* Extract hooks from Copilot hooks JSON into canonical format.
|
|
23772
23820
|
* Copilot format: { version: 1, hooks: { eventName: [...hookEntries] } }
|
|
@@ -23787,12 +23835,14 @@ function copilotHooksToCanonical(copilotHooks, logger) {
|
|
|
23787
23835
|
const entry = parseResult.data;
|
|
23788
23836
|
const { command, shell } = resolveImportCommand$1(entry, logger);
|
|
23789
23837
|
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
23838
|
+
const passthrough = importPassthrough$1(entry);
|
|
23839
|
+
if (command === void 0 && hasOsOverride(passthrough)) logger?.warn(`Copilot hook on '${copilotEventName}' has only VS Code per-OS overrides (windows/linux/osx) and no portable command; it is preserved for copilot, but other targets will generate it without a command until a \`command\` is added.`);
|
|
23790
23840
|
defs.push({
|
|
23791
23841
|
type: "command",
|
|
23792
23842
|
...command !== void 0 && { command },
|
|
23793
23843
|
...shell !== void 0 && { shell },
|
|
23794
23844
|
...timeout !== void 0 && { timeout },
|
|
23795
|
-
...
|
|
23845
|
+
...passthrough
|
|
23796
23846
|
});
|
|
23797
23847
|
}
|
|
23798
23848
|
if (defs.length > 0) canonical[eventName] = defs;
|
|
@@ -23955,6 +24005,8 @@ const CopilotCliHookEntrySchema = z.looseObject({
|
|
|
23955
24005
|
bash: z.optional(z.string()),
|
|
23956
24006
|
powershell: z.optional(z.string()),
|
|
23957
24007
|
command: z.optional(z.string()),
|
|
24008
|
+
exec: z.optional(z.string()),
|
|
24009
|
+
args: z.optional(z.array(z.string())),
|
|
23958
24010
|
prompt: z.optional(z.string()),
|
|
23959
24011
|
url: z.optional(z.string()),
|
|
23960
24012
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
@@ -23984,6 +24036,32 @@ function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger
|
|
|
23984
24036
|
return {};
|
|
23985
24037
|
}
|
|
23986
24038
|
/**
|
|
24039
|
+
* Pick the command fields of an exported `command` entry.
|
|
24040
|
+
*
|
|
24041
|
+
* A canonical `args` list (an empty one counts) selects the CLI's shell-free
|
|
24042
|
+
* form: `command` becomes `exec` and the list is written as `args`, so the
|
|
24043
|
+
* executable is spawned directly. The `shell` selector is meaningless there
|
|
24044
|
+
* and is dropped with a warning. Otherwise the canonical `shell` selector
|
|
24045
|
+
* picks the shell-specific field; without it the portable `command` field is
|
|
24046
|
+
* written, which upstream copies to both `bash` and `powershell`. Keying off
|
|
24047
|
+
* `shell` rather than `process.platform` keeps the generated file identical
|
|
24048
|
+
* on every machine.
|
|
24049
|
+
*/
|
|
24050
|
+
function resolveExportCommandPart({ def, eventName, logger }) {
|
|
24051
|
+
if (Array.isArray(def.args)) {
|
|
24052
|
+
if (def.shell !== void 0) logger?.warn(`Copilot CLI runs an \`exec\` hook without a shell; dropping shell "${def.shell}" on '${eventName}'.`);
|
|
24053
|
+
return compact({
|
|
24054
|
+
exec: def.command,
|
|
24055
|
+
args: def.args,
|
|
24056
|
+
env: def.env
|
|
24057
|
+
});
|
|
24058
|
+
}
|
|
24059
|
+
return compact({
|
|
24060
|
+
[def.shell ?? "command"]: def.command,
|
|
24061
|
+
env: def.env
|
|
24062
|
+
});
|
|
24063
|
+
}
|
|
24064
|
+
/**
|
|
23987
24065
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
23988
24066
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
23989
24067
|
*/
|
|
@@ -24023,19 +24101,17 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
24023
24101
|
...timeoutPart,
|
|
24024
24102
|
...rest
|
|
24025
24103
|
});
|
|
24026
|
-
else if (hookType === "command") {
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
|
|
24033
|
-
|
|
24034
|
-
|
|
24035
|
-
|
|
24036
|
-
|
|
24037
|
-
});
|
|
24038
|
-
}
|
|
24104
|
+
else if (hookType === "command") entries.push({
|
|
24105
|
+
type: "command",
|
|
24106
|
+
...matcherPart,
|
|
24107
|
+
...resolveExportCommandPart({
|
|
24108
|
+
def,
|
|
24109
|
+
eventName,
|
|
24110
|
+
logger
|
|
24111
|
+
}),
|
|
24112
|
+
...timeoutPart,
|
|
24113
|
+
...rest
|
|
24114
|
+
});
|
|
24039
24115
|
}
|
|
24040
24116
|
return entries;
|
|
24041
24117
|
}
|
|
@@ -24075,6 +24151,10 @@ function importPassthrough(entry) {
|
|
|
24075
24151
|
/**
|
|
24076
24152
|
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
24077
24153
|
*
|
|
24154
|
+
* The CLI-only `exec` form maps to canonical `command` + `args`, the exec form
|
|
24155
|
+
* shared with AugmentCode and Claude Code, so it no longer imports as a hook
|
|
24156
|
+
* with no command at all.
|
|
24157
|
+
*
|
|
24078
24158
|
* A shell-specific field carries its `shell` through so re-export writes the
|
|
24079
24159
|
* same field back. An entry using only the portable `command` field leaves
|
|
24080
24160
|
* `shell` unset, which re-export renders as the portable field again.
|
|
@@ -24088,6 +24168,13 @@ function importPassthrough(entry) {
|
|
|
24088
24168
|
function resolveImportCommand(entry, logger) {
|
|
24089
24169
|
const hasBash = typeof entry.bash === "string";
|
|
24090
24170
|
const hasPowershell = typeof entry.powershell === "string";
|
|
24171
|
+
if (typeof entry.exec === "string") {
|
|
24172
|
+
if (hasBash || hasPowershell || typeof entry.command === "string") logger?.warn("Copilot CLI hook has both exec and a shell command; using exec and ignoring the shell fields, as the hooks reference says not to combine them.");
|
|
24173
|
+
return {
|
|
24174
|
+
command: entry.exec,
|
|
24175
|
+
args: entry.args ?? []
|
|
24176
|
+
};
|
|
24177
|
+
}
|
|
24091
24178
|
if (hasBash && hasPowershell) {
|
|
24092
24179
|
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.");
|
|
24093
24180
|
return {
|
|
@@ -24137,11 +24224,12 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
24137
24224
|
...passthrough
|
|
24138
24225
|
});
|
|
24139
24226
|
else {
|
|
24140
|
-
const { command, shell } = resolveImportCommand(entry, logger);
|
|
24227
|
+
const { command, shell, args } = resolveImportCommand(entry, logger);
|
|
24141
24228
|
defs.push({
|
|
24142
24229
|
type: "command",
|
|
24143
24230
|
...command !== void 0 && { command },
|
|
24144
24231
|
...shell !== void 0 && { shell },
|
|
24232
|
+
...args !== void 0 && { args },
|
|
24145
24233
|
...matcherPart,
|
|
24146
24234
|
...timeoutPart,
|
|
24147
24235
|
...passthrough
|
|
@@ -24691,6 +24779,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
24691
24779
|
...def.loop_limit !== void 0 && { loop_limit: def.loop_limit },
|
|
24692
24780
|
...def.matcher !== void 0 && def.matcher !== null && { matcher: def.matcher },
|
|
24693
24781
|
...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
|
|
24782
|
+
...def.type === "prompt" && def.model !== void 0 && def.model !== null && { model: def.model },
|
|
24694
24783
|
...def.failClosed !== void 0 && def.failClosed !== null && { failClosed: def.failClosed }
|
|
24695
24784
|
}));
|
|
24696
24785
|
if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
|
|
@@ -37028,6 +37117,363 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
37028
37117
|
}
|
|
37029
37118
|
};
|
|
37030
37119
|
//#endregion
|
|
37120
|
+
//#region src/constants/pool-paths.ts
|
|
37121
|
+
/**
|
|
37122
|
+
* Pool (Poolside's coding agent CLI) configuration-layout conventions.
|
|
37123
|
+
*
|
|
37124
|
+
* Pool reads `AGENTS.md` instruction files the same way the AGENTS.md standard
|
|
37125
|
+
* describes them: the personal `~/.config/poolside/AGENTS.md` (Pool itself
|
|
37126
|
+
* honours `XDG_CONFIG_HOME` upstream; rulesync writes only the XDG-default
|
|
37127
|
+
* path), the project-root `AGENTS.md`, and nested per-directory
|
|
37128
|
+
* `AGENTS.md` files from the repository root down through the working
|
|
37129
|
+
* directory, deeper files taking precedence. It skips ignored directories
|
|
37130
|
+
* (`.git/`, `node_modules/`, cache directories, repository ignore rules).
|
|
37131
|
+
*
|
|
37132
|
+
* Skills follow the Agent Skills format (`<name>/SKILL.md` bundles). Pool
|
|
37133
|
+
* scans `.poolside/skills/` (project) and `~/.config/poolside/skills/`
|
|
37134
|
+
* (global) plus the shared `.agents/skills/` / `~/.agents/skills/` roots and
|
|
37135
|
+
* the skill directories of other Agent Skills tools; rulesync writes only the
|
|
37136
|
+
* Pool-specific roots and leaves the shared ones to their own targets.
|
|
37137
|
+
*
|
|
37138
|
+
* @see https://docs.poolside.ai/agent-instructions
|
|
37139
|
+
* @see https://docs.poolside.ai/skills
|
|
37140
|
+
* @see https://github.com/poolsideai/pool
|
|
37141
|
+
*/
|
|
37142
|
+
/** Project-scoped `.poolside/` directory at the project root. */
|
|
37143
|
+
const POOL_DIR = ".poolside";
|
|
37144
|
+
/** Global config directory for Pool, relative to the home directory. */
|
|
37145
|
+
const POOL_GLOBAL_DIR = join(".config", "poolside");
|
|
37146
|
+
/** Project skills root, relative to the project root. */
|
|
37147
|
+
const POOL_SKILLS_DIR_PATH = join(POOL_DIR, "skills");
|
|
37148
|
+
/** Global skills root, relative to the home directory. */
|
|
37149
|
+
const POOL_GLOBAL_SKILLS_DIR_PATH = join(POOL_GLOBAL_DIR, "skills");
|
|
37150
|
+
/**
|
|
37151
|
+
* Pool's settings file name. MCP servers live under the top-level
|
|
37152
|
+
* `mcp_servers` key of `.poolside/settings.yaml` (project, committed) and
|
|
37153
|
+
* `~/.config/poolside/settings.yaml` (global); the untracked
|
|
37154
|
+
* `.poolside/settings.local.yaml` overlay is left to the user.
|
|
37155
|
+
*
|
|
37156
|
+
* @see https://docs.poolside.ai/mcp-servers
|
|
37157
|
+
* @see https://docs.poolside.ai/settings-file-reference
|
|
37158
|
+
*/
|
|
37159
|
+
const POOL_SETTINGS_FILE_NAME = "settings.yaml";
|
|
37160
|
+
/** Top-level key of Pool's settings file that holds the MCP server map. */
|
|
37161
|
+
const POOL_MCP_SERVERS_KEY = "mcp_servers";
|
|
37162
|
+
//#endregion
|
|
37163
|
+
//#region src/features/mcp/pool-mcp.ts
|
|
37164
|
+
/**
|
|
37165
|
+
* Single spelling of the settings.yaml codec/policy, matching the
|
|
37166
|
+
* `SHARED_CONFIG_OWNERSHIP` declaration for both scopes: fail closed on an
|
|
37167
|
+
* unparseable root rather than replacing the user's primary Pool settings with
|
|
37168
|
+
* generated output.
|
|
37169
|
+
*/
|
|
37170
|
+
function parsePoolSettings(fileContent, filePath) {
|
|
37171
|
+
return parseSharedConfig({
|
|
37172
|
+
format: "yaml",
|
|
37173
|
+
fileContent,
|
|
37174
|
+
filePath,
|
|
37175
|
+
invalidRootPolicy: "error"
|
|
37176
|
+
});
|
|
37177
|
+
}
|
|
37178
|
+
/**
|
|
37179
|
+
* The remote transports Pool's `transport.type` accepts. `streamable-http` is
|
|
37180
|
+
* the canonical rulesync name for what Pool calls `http`; everything else is
|
|
37181
|
+
* left to the caller to reject.
|
|
37182
|
+
*
|
|
37183
|
+
* @see https://docs.poolside.ai/mcp-servers
|
|
37184
|
+
*/
|
|
37185
|
+
function asPoolRemoteType(stated, url) {
|
|
37186
|
+
if (stated === "sse") return "sse";
|
|
37187
|
+
if (stated === "http" || stated === "streamable-http") return "http";
|
|
37188
|
+
if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
|
|
37189
|
+
}
|
|
37190
|
+
/**
|
|
37191
|
+
* Pool spells remote headers as a list of `"Name: value"` strings rather than
|
|
37192
|
+
* a map, so the canonical record is flattened at generate time and split back
|
|
37193
|
+
* at the first `:` on import. A list entry without a `:` names no header and
|
|
37194
|
+
* is dropped with a warning — silently losing what may be a mistyped auth
|
|
37195
|
+
* header would leave the canonical file short of one without a trace.
|
|
37196
|
+
*/
|
|
37197
|
+
function headersRecordToPoolList(headers) {
|
|
37198
|
+
return Object.entries(headers).map(([name, value]) => `${name}: ${value}`);
|
|
37199
|
+
}
|
|
37200
|
+
function poolHeadersListToRecord(serverName, headers) {
|
|
37201
|
+
if (!isStringArray$2(headers)) return void 0;
|
|
37202
|
+
const result = {};
|
|
37203
|
+
for (const entry of headers) {
|
|
37204
|
+
const separator = entry.indexOf(":");
|
|
37205
|
+
const name = separator > 0 ? entry.slice(0, separator).trim() : "";
|
|
37206
|
+
if (name === "" || PROTOTYPE_POLLUTION_KEYS.has(name)) {
|
|
37207
|
+
warnWithFallback(void 0, `Ignored malformed header ${quoteValueForWarning(entry)} in Pool MCP server ${quoteValueForWarning(serverName)}: expected "Name: value"`);
|
|
37208
|
+
continue;
|
|
37209
|
+
}
|
|
37210
|
+
result[name] = entry.slice(separator + 1).trim();
|
|
37211
|
+
}
|
|
37212
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
37213
|
+
}
|
|
37214
|
+
/**
|
|
37215
|
+
* Pool's `deny`/`enabled_tools` entries are glob patterns, while the canonical
|
|
37216
|
+
* `enabledTools`/`disabledTools` lists are literal tool names that the other
|
|
37217
|
+
* targets match verbatim. A pattern therefore imports as a name that matches
|
|
37218
|
+
* nothing elsewhere — a `deny` that quietly stops denying is the case worth
|
|
37219
|
+
* calling out — so it is copied as-is but warned about. `allow` is exempt: it
|
|
37220
|
+
* imports to the Pool-only `poolAllow`, where globs are exactly what Pool
|
|
37221
|
+
* expects and no other tool ever reads them.
|
|
37222
|
+
*/
|
|
37223
|
+
const GLOB_METACHARACTERS = /[*?[]/;
|
|
37224
|
+
/** Patterns named in the glob warning before the rest collapse to `(+N more)`. */
|
|
37225
|
+
const MAX_LISTED_GLOB_PATTERNS = 10;
|
|
37226
|
+
function importPoolToolList({ serverName, poolKey, value, warnOnGlobs }) {
|
|
37227
|
+
if (!isStringArray$2(value)) {
|
|
37228
|
+
warnWithFallback(void 0, `Ignored malformed value for ${poolKey} in Pool MCP server ${quoteValueForWarning(serverName)}: expected a list of strings`);
|
|
37229
|
+
return;
|
|
37230
|
+
}
|
|
37231
|
+
if (!warnOnGlobs) return value;
|
|
37232
|
+
const patterns = value.filter((entry) => GLOB_METACHARACTERS.test(entry));
|
|
37233
|
+
if (patterns.length > 0) {
|
|
37234
|
+
const listed = patterns.slice(0, MAX_LISTED_GLOB_PATTERNS).map(quoteValueForWarning);
|
|
37235
|
+
const overflow = patterns.length - listed.length;
|
|
37236
|
+
const shown = overflow > 0 ? `${listed.join(", ")} (+${overflow} more)` : listed.join(", ");
|
|
37237
|
+
warnWithFallback(void 0, `${poolKey} in Pool MCP server ${quoteValueForWarning(serverName)} contains glob patterns (${shown}); other tools match the imported tool names literally`);
|
|
37238
|
+
}
|
|
37239
|
+
return value;
|
|
37240
|
+
}
|
|
37241
|
+
/**
|
|
37242
|
+
* Pool's `transport` block for a remote server, or `null` (after a warning)
|
|
37243
|
+
* when the server has no url or names a transport Pool cannot reach.
|
|
37244
|
+
*/
|
|
37245
|
+
function toPoolTransport(name, config, logger) {
|
|
37246
|
+
const url = resolveRemoteMcpUrl(config);
|
|
37247
|
+
if (!url) return warnAndSkipMcpServer({
|
|
37248
|
+
toolName: "Pool",
|
|
37249
|
+
serverName: name,
|
|
37250
|
+
reason: "a remote transport without a url",
|
|
37251
|
+
logger
|
|
37252
|
+
});
|
|
37253
|
+
const stated = config.type ?? config.transport;
|
|
37254
|
+
const type = asPoolRemoteType(stated, url);
|
|
37255
|
+
if (type === void 0) return warnAndSkipMcpServer({
|
|
37256
|
+
toolName: "Pool",
|
|
37257
|
+
serverName: name,
|
|
37258
|
+
reason: stated === void 0 ? "a WebSocket url, which Pool's remote transports (http and sse) cannot reach" : `the "${stated}" transport, which Pool does not offer for remote servers (only http and sse)`,
|
|
37259
|
+
logger
|
|
37260
|
+
});
|
|
37261
|
+
const transport = {
|
|
37262
|
+
type,
|
|
37263
|
+
url
|
|
37264
|
+
};
|
|
37265
|
+
if (config.headers && Object.keys(config.headers).length > 0) transport.headers = headersRecordToPoolList(config.headers);
|
|
37266
|
+
return transport;
|
|
37267
|
+
}
|
|
37268
|
+
/**
|
|
37269
|
+
* The transport-specific part of a Pool server entry: a `transport` block for
|
|
37270
|
+
* remote servers, `command`/`args`/`cwd` for stdio ones. `null` (after a
|
|
37271
|
+
* warning) when the server cannot be written.
|
|
37272
|
+
*/
|
|
37273
|
+
function toPoolTransportFields(name, config, logger) {
|
|
37274
|
+
if (isRemoteMcpServer(config)) {
|
|
37275
|
+
const transport = toPoolTransport(name, config, logger);
|
|
37276
|
+
return transport === null ? null : { transport };
|
|
37277
|
+
}
|
|
37278
|
+
const [command, ...args] = resolveLocalMcpCommand(config);
|
|
37279
|
+
if (!command) return warnAndSkipMcpServer({
|
|
37280
|
+
toolName: "Pool",
|
|
37281
|
+
serverName: name,
|
|
37282
|
+
reason: "a stdio transport without a command",
|
|
37283
|
+
logger
|
|
37284
|
+
});
|
|
37285
|
+
const fields = {
|
|
37286
|
+
command,
|
|
37287
|
+
args
|
|
37288
|
+
};
|
|
37289
|
+
if (config.cwd) fields.cwd = config.cwd;
|
|
37290
|
+
return fields;
|
|
37291
|
+
}
|
|
37292
|
+
/**
|
|
37293
|
+
* The fields shared by stdio and remote entries. Tool filters map onto Pool's
|
|
37294
|
+
* own switches — `enabledTools` becomes `enabled_tools` (the tools Pool
|
|
37295
|
+
* exposes at all) and `disabledTools` becomes `deny` (patterns agents may
|
|
37296
|
+
* never use); the Pool-only approval allowlist is authored as `poolAllow` and
|
|
37297
|
+
* written as `allow`. `disabled: true` is spelled the same on both sides.
|
|
37298
|
+
*/
|
|
37299
|
+
function toPoolCommonFields(config) {
|
|
37300
|
+
const fields = {};
|
|
37301
|
+
if (config.env && Object.keys(config.env).length > 0) fields.env = config.env;
|
|
37302
|
+
if (config.enabledTools && config.enabledTools.length > 0) fields.enabled_tools = config.enabledTools;
|
|
37303
|
+
if (config.poolAllow && config.poolAllow.length > 0) fields.allow = config.poolAllow;
|
|
37304
|
+
if (config.disabledTools && config.disabledTools.length > 0) fields.deny = config.disabledTools;
|
|
37305
|
+
if (config.disabled === true) fields.disabled = true;
|
|
37306
|
+
return fields;
|
|
37307
|
+
}
|
|
37308
|
+
/**
|
|
37309
|
+
* Convert canonical rulesync servers to Pool's native `mcp_servers` shape:
|
|
37310
|
+
* stdio servers carry `command`/`args`/`cwd`/`env`, remote servers carry a
|
|
37311
|
+
* `transport` block (`type` of `http` or `sse`, `url`, optional `headers`
|
|
37312
|
+
* list) plus optional `env`; see `toPoolCommonFields` for the tool filters.
|
|
37313
|
+
*
|
|
37314
|
+
* @see https://docs.poolside.ai/mcp-servers
|
|
37315
|
+
*/
|
|
37316
|
+
function convertToPoolFormat(mcpServers, logger) {
|
|
37317
|
+
const result = {};
|
|
37318
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
37319
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
37320
|
+
if (!isRecord$1(config)) continue;
|
|
37321
|
+
if (declaresNoTransport(config)) {
|
|
37322
|
+
warnAndSkipMcpServer({
|
|
37323
|
+
toolName: "Pool",
|
|
37324
|
+
serverName: name,
|
|
37325
|
+
reason: "no transport",
|
|
37326
|
+
logger
|
|
37327
|
+
});
|
|
37328
|
+
continue;
|
|
37329
|
+
}
|
|
37330
|
+
const transportFields = toPoolTransportFields(name, config, logger);
|
|
37331
|
+
if (transportFields === null) continue;
|
|
37332
|
+
result[name] = {
|
|
37333
|
+
...transportFields,
|
|
37334
|
+
...toPoolCommonFields(config)
|
|
37335
|
+
};
|
|
37336
|
+
}
|
|
37337
|
+
return result;
|
|
37338
|
+
}
|
|
37339
|
+
const POOL_TO_CANONICAL_TOOL_LIST_KEYS = {
|
|
37340
|
+
enabled_tools: "enabledTools",
|
|
37341
|
+
deny: "disabledTools",
|
|
37342
|
+
allow: "poolAllow"
|
|
37343
|
+
};
|
|
37344
|
+
/**
|
|
37345
|
+
* Convert Pool's native `mcp_servers` shape back to canonical rulesync
|
|
37346
|
+
* servers: the `transport` block is flattened to `type`/`url`/`headers`,
|
|
37347
|
+
* `enabled_tools` maps back to `enabledTools`, `deny` to `disabledTools` and
|
|
37348
|
+
* `allow` to the namespaced `poolAllow` (see `McpServerSchema`). Unknown keys
|
|
37349
|
+
* pass through untouched so an import keeps whatever a hand-authored entry
|
|
37350
|
+
* declared; generation back out is a whitelist, so only the keys
|
|
37351
|
+
* `convertToPoolFormat` writes are re-emitted.
|
|
37352
|
+
*/
|
|
37353
|
+
function convertFromPoolFormat(poolServers) {
|
|
37354
|
+
const result = {};
|
|
37355
|
+
for (const [name, config] of Object.entries(poolServers)) {
|
|
37356
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
37357
|
+
const converted = {};
|
|
37358
|
+
for (const [key, value] of Object.entries(config)) {
|
|
37359
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
37360
|
+
if (key === "transport") {
|
|
37361
|
+
if (!isRecord$1(value)) continue;
|
|
37362
|
+
if (typeof value.type === "string") converted.type = value.type;
|
|
37363
|
+
if (typeof value.url === "string") converted.url = value.url;
|
|
37364
|
+
const headers = poolHeadersListToRecord(name, value.headers);
|
|
37365
|
+
if (headers) converted.headers = headers;
|
|
37366
|
+
continue;
|
|
37367
|
+
}
|
|
37368
|
+
const canonicalKey = Object.hasOwn(POOL_TO_CANONICAL_TOOL_LIST_KEYS, key) ? POOL_TO_CANONICAL_TOOL_LIST_KEYS[key] : void 0;
|
|
37369
|
+
if (canonicalKey) {
|
|
37370
|
+
const list = importPoolToolList({
|
|
37371
|
+
serverName: name,
|
|
37372
|
+
poolKey: key,
|
|
37373
|
+
value,
|
|
37374
|
+
warnOnGlobs: canonicalKey !== "poolAllow"
|
|
37375
|
+
});
|
|
37376
|
+
if (list) converted[canonicalKey] = list;
|
|
37377
|
+
continue;
|
|
37378
|
+
}
|
|
37379
|
+
converted[key] = value;
|
|
37380
|
+
}
|
|
37381
|
+
result[name] = converted;
|
|
37382
|
+
}
|
|
37383
|
+
return result;
|
|
37384
|
+
}
|
|
37385
|
+
/**
|
|
37386
|
+
* Pool MCP servers.
|
|
37387
|
+
*
|
|
37388
|
+
* Pool reads MCP servers from the `mcp_servers` block of its settings file:
|
|
37389
|
+
* `<project>/.poolside/settings.yaml` at project scope (shared and committed)
|
|
37390
|
+
* and `~/.config/poolside/settings.yaml` at user scope. The untracked
|
|
37391
|
+
* `.poolside/settings.local.yaml` overlay, which Pool layers on top of both,
|
|
37392
|
+
* is left to the user. Every other top-level settings key is preserved and
|
|
37393
|
+
* the file is never deleted.
|
|
37394
|
+
*
|
|
37395
|
+
* @see https://docs.poolside.ai/mcp-servers
|
|
37396
|
+
* @see https://docs.poolside.ai/settings-file-reference
|
|
37397
|
+
*/
|
|
37398
|
+
var PoolMcp = class PoolMcp extends ToolMcp {
|
|
37399
|
+
settings;
|
|
37400
|
+
constructor(params) {
|
|
37401
|
+
super(params);
|
|
37402
|
+
this.settings = parsePoolSettings(this.fileContent ?? "", join(this.relativeDirPath, this.relativeFilePath));
|
|
37403
|
+
}
|
|
37404
|
+
getSettings() {
|
|
37405
|
+
return this.settings;
|
|
37406
|
+
}
|
|
37407
|
+
isDeletable() {
|
|
37408
|
+
return false;
|
|
37409
|
+
}
|
|
37410
|
+
static getSettablePaths({ global = false } = {}) {
|
|
37411
|
+
return {
|
|
37412
|
+
relativeDirPath: global ? POOL_GLOBAL_DIR : POOL_DIR,
|
|
37413
|
+
relativeFilePath: POOL_SETTINGS_FILE_NAME
|
|
37414
|
+
};
|
|
37415
|
+
}
|
|
37416
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
37417
|
+
const paths = this.getSettablePaths({ global });
|
|
37418
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
37419
|
+
return new PoolMcp({
|
|
37420
|
+
outputRoot,
|
|
37421
|
+
relativeDirPath: paths.relativeDirPath,
|
|
37422
|
+
relativeFilePath: paths.relativeFilePath,
|
|
37423
|
+
fileContent,
|
|
37424
|
+
validate,
|
|
37425
|
+
global
|
|
37426
|
+
});
|
|
37427
|
+
}
|
|
37428
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
37429
|
+
const paths = this.getSettablePaths({ global });
|
|
37430
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
37431
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
37432
|
+
const converted = convertToPoolFormat(Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([serverName, serverConfig]) => {
|
|
37433
|
+
const rawServer = rulesyncMcp.getRawMcpServer(serverName);
|
|
37434
|
+
const poolAllow = isRecord$1(rawServer) ? rawServer.poolAllow : void 0;
|
|
37435
|
+
return [serverName, {
|
|
37436
|
+
...serverConfig,
|
|
37437
|
+
...isStringArray$2(poolAllow) && { poolAllow }
|
|
37438
|
+
}];
|
|
37439
|
+
})), logger);
|
|
37440
|
+
return new PoolMcp({
|
|
37441
|
+
outputRoot,
|
|
37442
|
+
relativeDirPath: paths.relativeDirPath,
|
|
37443
|
+
relativeFilePath: paths.relativeFilePath,
|
|
37444
|
+
fileContent: applySharedConfigPatch({
|
|
37445
|
+
fileKey: sharedConfigFileKey(paths),
|
|
37446
|
+
feature: "mcp",
|
|
37447
|
+
existingContent,
|
|
37448
|
+
patch: { [POOL_MCP_SERVERS_KEY]: converted },
|
|
37449
|
+
filePath
|
|
37450
|
+
}),
|
|
37451
|
+
validate,
|
|
37452
|
+
global
|
|
37453
|
+
});
|
|
37454
|
+
}
|
|
37455
|
+
toRulesyncMcp() {
|
|
37456
|
+
const converted = convertFromPoolFormat(isRecord$1(this.settings["mcp_servers"]) ? this.settings[POOL_MCP_SERVERS_KEY] : {});
|
|
37457
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: converted }, null, 2) });
|
|
37458
|
+
}
|
|
37459
|
+
validate() {
|
|
37460
|
+
return {
|
|
37461
|
+
success: true,
|
|
37462
|
+
error: null
|
|
37463
|
+
};
|
|
37464
|
+
}
|
|
37465
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
37466
|
+
return new PoolMcp({
|
|
37467
|
+
outputRoot,
|
|
37468
|
+
relativeDirPath,
|
|
37469
|
+
relativeFilePath,
|
|
37470
|
+
fileContent: "",
|
|
37471
|
+
validate: false,
|
|
37472
|
+
global
|
|
37473
|
+
});
|
|
37474
|
+
}
|
|
37475
|
+
};
|
|
37476
|
+
//#endregion
|
|
37031
37477
|
//#region src/features/mcp/qwencode-mcp.ts
|
|
37032
37478
|
/**
|
|
37033
37479
|
* Map rulesync's canonical per-server tool filters to Qwen Code's field names.
|
|
@@ -39475,6 +39921,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
39475
39921
|
supportsDisabledTools: true
|
|
39476
39922
|
}
|
|
39477
39923
|
}],
|
|
39924
|
+
["pool", {
|
|
39925
|
+
class: PoolMcp,
|
|
39926
|
+
meta: {
|
|
39927
|
+
supportsProject: true,
|
|
39928
|
+
supportsGlobal: true,
|
|
39929
|
+
supportsEnabledTools: true,
|
|
39930
|
+
supportsDisabledTools: true
|
|
39931
|
+
}
|
|
39932
|
+
}],
|
|
39478
39933
|
["qwencode", {
|
|
39479
39934
|
class: QwencodeMcp,
|
|
39480
39935
|
meta: {
|
|
@@ -56696,7 +57151,7 @@ const AmpSkillFrontmatterSchema = z.looseObject({
|
|
|
56696
57151
|
* Amp natively reads Agent Skills following the open standard: a directory with a
|
|
56697
57152
|
* `SKILL.md` carrying `name`/`description` frontmatter, scoped to the project
|
|
56698
57153
|
* (`.agents/skills/`) and user-wide (`~/.config/agents/skills/`).
|
|
56699
|
-
* @see https://ampcode.com/
|
|
57154
|
+
* @see https://ampcode.com/docs/customize/skills
|
|
56700
57155
|
*/
|
|
56701
57156
|
var AmpSkill = class AmpSkill extends ToolSkill {
|
|
56702
57157
|
constructor({ outputRoot = process.cwd(), relativeDirPath = AMP_SKILLS_PROJECT_DIR, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -59175,8 +59630,11 @@ const CursorSkillFrontmatterSchema = z.looseObject({
|
|
|
59175
59630
|
name: z.string(),
|
|
59176
59631
|
description: z.string(),
|
|
59177
59632
|
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
59633
|
+
globs: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
59178
59634
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
59179
59635
|
"user-invocable": z.optional(z.boolean()),
|
|
59636
|
+
icon: z.optional(z.string()),
|
|
59637
|
+
color: z.optional(z.string()),
|
|
59180
59638
|
metadata: z.optional(z.looseObject({}))
|
|
59181
59639
|
});
|
|
59182
59640
|
/**
|
|
@@ -59228,10 +59686,13 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
59228
59686
|
}
|
|
59229
59687
|
toRulesyncSkill() {
|
|
59230
59688
|
const frontmatter = this.getFrontmatter();
|
|
59689
|
+
const paths = frontmatter.paths ?? frontmatter.globs;
|
|
59231
59690
|
const cursorSection = {
|
|
59232
|
-
...
|
|
59691
|
+
...paths !== void 0 && { paths },
|
|
59233
59692
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
59234
59693
|
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
59694
|
+
...frontmatter.icon !== void 0 && { icon: frontmatter.icon },
|
|
59695
|
+
...frontmatter.color !== void 0 && { color: frontmatter.color },
|
|
59235
59696
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
59236
59697
|
};
|
|
59237
59698
|
const rulesyncFrontmatter = {
|
|
@@ -59273,6 +59734,8 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
59273
59734
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
59274
59735
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
59275
59736
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
59737
|
+
...cursorSection?.icon !== void 0 && { icon: cursorSection.icon },
|
|
59738
|
+
...cursorSection?.color !== void 0 && { color: cursorSection.color },
|
|
59276
59739
|
...metadata !== void 0 && { metadata }
|
|
59277
59740
|
};
|
|
59278
59741
|
return new CursorSkill({
|
|
@@ -61657,37 +62120,6 @@ var PiSkill = class PiSkill extends ToolSkill {
|
|
|
61657
62120
|
}
|
|
61658
62121
|
};
|
|
61659
62122
|
//#endregion
|
|
61660
|
-
//#region src/constants/pool-paths.ts
|
|
61661
|
-
/**
|
|
61662
|
-
* Pool (Poolside's coding agent CLI) configuration-layout conventions.
|
|
61663
|
-
*
|
|
61664
|
-
* Pool reads `AGENTS.md` instruction files the same way the AGENTS.md standard
|
|
61665
|
-
* describes them: the personal `~/.config/poolside/AGENTS.md` (Pool itself
|
|
61666
|
-
* honours `XDG_CONFIG_HOME` upstream; rulesync writes only the XDG-default
|
|
61667
|
-
* path), the project-root `AGENTS.md`, and nested per-directory
|
|
61668
|
-
* `AGENTS.md` files from the repository root down through the working
|
|
61669
|
-
* directory, deeper files taking precedence. It skips ignored directories
|
|
61670
|
-
* (`.git/`, `node_modules/`, cache directories, repository ignore rules).
|
|
61671
|
-
*
|
|
61672
|
-
* Skills follow the Agent Skills format (`<name>/SKILL.md` bundles). Pool
|
|
61673
|
-
* scans `.poolside/skills/` (project) and `~/.config/poolside/skills/`
|
|
61674
|
-
* (global) plus the shared `.agents/skills/` / `~/.agents/skills/` roots and
|
|
61675
|
-
* the skill directories of other Agent Skills tools; rulesync writes only the
|
|
61676
|
-
* Pool-specific roots and leaves the shared ones to their own targets.
|
|
61677
|
-
*
|
|
61678
|
-
* @see https://docs.poolside.ai/agent-instructions
|
|
61679
|
-
* @see https://docs.poolside.ai/skills
|
|
61680
|
-
* @see https://github.com/poolsideai/pool
|
|
61681
|
-
*/
|
|
61682
|
-
/** Project-scoped `.poolside/` directory at the project root. */
|
|
61683
|
-
const POOL_DIR = ".poolside";
|
|
61684
|
-
/** Global config directory for Pool, relative to the home directory. */
|
|
61685
|
-
const POOL_GLOBAL_DIR = join(".config", "poolside");
|
|
61686
|
-
/** Project skills root, relative to the project root. */
|
|
61687
|
-
const POOL_SKILLS_DIR_PATH = join(POOL_DIR, "skills");
|
|
61688
|
-
/** Global skills root, relative to the home directory. */
|
|
61689
|
-
const POOL_GLOBAL_SKILLS_DIR_PATH = join(POOL_GLOBAL_DIR, "skills");
|
|
61690
|
-
//#endregion
|
|
61691
62123
|
//#region src/features/skills/pool-skill.ts
|
|
61692
62124
|
const PoolSkillFrontmatterSchema = z.looseObject({
|
|
61693
62125
|
name: z.string(),
|
|
@@ -66032,11 +66464,17 @@ var CopilotSubagent = class CopilotSubagent extends ToolSubagent {
|
|
|
66032
66464
|
*
|
|
66033
66465
|
* Reference: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli
|
|
66034
66466
|
* https://docs.github.com/en/copilot/reference/custom-agents-configuration
|
|
66467
|
+
* https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference
|
|
66035
66468
|
*
|
|
66036
66469
|
* Per the configuration docs:
|
|
66037
66470
|
* - `description` is required.
|
|
66038
66471
|
* - `name`, `target`, `tools`, `model`, `disable-model-invocation`,
|
|
66039
66472
|
* `user-invocable`, `mcp-servers`, and `metadata` are optional.
|
|
66473
|
+
* - The CLI command reference adds `models` (authored models in priority
|
|
66474
|
+
* order, overriding `model` when both are set) and `modelPolicy`
|
|
66475
|
+
* (`preferred` lets a `subagents` settings override or the `/subagents`
|
|
66476
|
+
* picker replace the model; `required` locks dispatch to the authored
|
|
66477
|
+
* list). CLI v1.0.81+; the shared reference page still lists `model` only.
|
|
66040
66478
|
*
|
|
66041
66479
|
* `looseObject` is used so that future additional fields are passed through
|
|
66042
66480
|
* unchanged (consistent with the rest of rulesync's frontmatter schemas).
|
|
@@ -66047,6 +66485,8 @@ const CopilotCliSubagentFrontmatterSchema = z.looseObject({
|
|
|
66047
66485
|
target: z.optional(z.string()),
|
|
66048
66486
|
tools: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
66049
66487
|
model: z.optional(z.string()),
|
|
66488
|
+
models: z.optional(z.array(z.string())),
|
|
66489
|
+
modelPolicy: z.optional(z.enum(["preferred", "required"])),
|
|
66050
66490
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
66051
66491
|
"user-invocable": z.optional(z.boolean()),
|
|
66052
66492
|
"mcp-servers": z.optional(z.record(z.string(), z.unknown())),
|
|
@@ -70423,16 +70863,131 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
70423
70863
|
}
|
|
70424
70864
|
};
|
|
70425
70865
|
//#endregion
|
|
70866
|
+
//#region src/utils/brace-aware-list.ts
|
|
70867
|
+
/**
|
|
70868
|
+
* Splits a comma-separated pattern list on the commas that separate its
|
|
70869
|
+
* entries, leaving the commas inside a brace group alone: in
|
|
70870
|
+
* `{src,lib}/**\/*.ts, tests/**\/*.test.ts` the first comma belongs to the
|
|
70871
|
+
* brace expansion and the second separates the two globs. Entries are trimmed;
|
|
70872
|
+
* empty entries are dropped.
|
|
70873
|
+
*
|
|
70874
|
+
* @example
|
|
70875
|
+
* splitBraceAwareList("src/**\/*.{ts,tsx}, docs/**")
|
|
70876
|
+
* // => ["src/**\/*.{ts,tsx}", "docs/**"]
|
|
70877
|
+
*/
|
|
70878
|
+
const splitBraceAwareList = (value) => {
|
|
70879
|
+
const entries = [];
|
|
70880
|
+
let current = "";
|
|
70881
|
+
let braceDepth = 0;
|
|
70882
|
+
for (const char of value) {
|
|
70883
|
+
if (char === "{") braceDepth += 1;
|
|
70884
|
+
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
70885
|
+
else if (char === "," && braceDepth === 0) {
|
|
70886
|
+
entries.push(current);
|
|
70887
|
+
current = "";
|
|
70888
|
+
continue;
|
|
70889
|
+
}
|
|
70890
|
+
current += char;
|
|
70891
|
+
}
|
|
70892
|
+
entries.push(current);
|
|
70893
|
+
return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
70894
|
+
};
|
|
70895
|
+
/**
|
|
70896
|
+
* Upper bound on the patterns one glob may expand to; past it the glob is
|
|
70897
|
+
* returned verbatim, since `{a,b}` repeated k times yields 2^k branches.
|
|
70898
|
+
*/
|
|
70899
|
+
const MAX_BRACE_EXPANSIONS = 256;
|
|
70900
|
+
/** Matches an innermost brace group with at least one comma: `{a,b}`, not `{a}` or `{a,{b,c}}`. */
|
|
70901
|
+
const INNERMOST_BRACE_GROUP_REGEX = /\{([^{},]*(?:,[^{},]*)+)\}/;
|
|
70902
|
+
/**
|
|
70903
|
+
* Expands the comma alternations of a glob (`a.{ts,tsx}` → `a.ts`, `a.tsx`)
|
|
70904
|
+
* into one pattern per combination, innermost group first, so the result can
|
|
70905
|
+
* be handed to a consumer that splits its pattern list on bare commas. A glob
|
|
70906
|
+
* without a brace alternation is returned as is; a brace group without a
|
|
70907
|
+
* comma (e.g. `{a}`) is left untouched. A glob whose expansion would exceed
|
|
70908
|
+
* `MAX_BRACE_EXPANSIONS` patterns is returned as is as well.
|
|
70909
|
+
*
|
|
70910
|
+
* The cap is enforced on the work list itself rather than by counting the
|
|
70911
|
+
* groups up front: a nested group such as `{{a,b},c}` is rewritten into one
|
|
70912
|
+
* new group per inner alternative, so the number of patterns in flight is the
|
|
70913
|
+
* only bound that holds for arbitrary nesting.
|
|
70914
|
+
*
|
|
70915
|
+
* @example
|
|
70916
|
+
* expandBraceAlternations("src/**\/*.{ts,tsx}")
|
|
70917
|
+
* // => ["src/**\/*.ts", "src/**\/*.tsx"]
|
|
70918
|
+
*/
|
|
70919
|
+
const expandBraceAlternations = (glob) => {
|
|
70920
|
+
let pending = [glob];
|
|
70921
|
+
for (;;) {
|
|
70922
|
+
const next = [];
|
|
70923
|
+
let expanded = false;
|
|
70924
|
+
for (const pattern of pending) {
|
|
70925
|
+
const match = INNERMOST_BRACE_GROUP_REGEX.exec(pattern);
|
|
70926
|
+
if (match === null) {
|
|
70927
|
+
next.push(pattern);
|
|
70928
|
+
continue;
|
|
70929
|
+
}
|
|
70930
|
+
expanded = true;
|
|
70931
|
+
const prefix = pattern.slice(0, match.index);
|
|
70932
|
+
const suffix = pattern.slice(match.index + match[0].length);
|
|
70933
|
+
for (const alternative of (match[1] ?? "").split(",")) next.push(`${prefix}${alternative}${suffix}`);
|
|
70934
|
+
if (next.length > MAX_BRACE_EXPANSIONS) return [glob];
|
|
70935
|
+
}
|
|
70936
|
+
if (!expanded) return [...new Set(next)];
|
|
70937
|
+
pending = next;
|
|
70938
|
+
}
|
|
70939
|
+
};
|
|
70940
|
+
//#endregion
|
|
70426
70941
|
//#region src/features/rules/aiassistant-rule.ts
|
|
70427
70942
|
/**
|
|
70943
|
+
* Rule types AI Assistant recognizes on the `apply:` metadata line. The values
|
|
70944
|
+
* are matched case-sensitively by the plugin, so they are emitted verbatim.
|
|
70945
|
+
* Any other value is carried through untouched (the plugin treats it as Off
|
|
70946
|
+
* today; a future build may recognize it).
|
|
70947
|
+
*/
|
|
70948
|
+
const AIASSISTANT_APPLY_VALUES = [
|
|
70949
|
+
"always",
|
|
70950
|
+
"manually",
|
|
70951
|
+
"by model decision",
|
|
70952
|
+
"by file patterns",
|
|
70953
|
+
"off"
|
|
70954
|
+
];
|
|
70955
|
+
const isAiassistantApply = (value) => AIASSISTANT_APPLY_VALUES.includes(value);
|
|
70956
|
+
/**
|
|
70957
|
+
* Globs that match the whole project and so say nothing beyond "always".
|
|
70958
|
+
*/
|
|
70959
|
+
const UNIVERSAL_GLOBS$2 = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
70960
|
+
const METADATA_DELIMITER = "---";
|
|
70961
|
+
/**
|
|
70962
|
+
* Splits text into lines that keep their own line terminator, so the body can
|
|
70963
|
+
* be sliced back out of the file byte-for-byte. Lone `\r` counts as a break,
|
|
70964
|
+
* matching Kotlin's `lines()`, which the plugin uses on the block.
|
|
70965
|
+
*/
|
|
70966
|
+
const LINE_SPLIT_REGEX = /(?<=\r\n|\r(?!\n)|(?<!\r)\n)/;
|
|
70967
|
+
/**
|
|
70968
|
+
* The block is line-based, so a value must not carry a line break: one would
|
|
70969
|
+
* start a new `<field>: <value>` line and could override `apply:`.
|
|
70970
|
+
*/
|
|
70971
|
+
const flattenValue = (value) => value.split(/\r\n|\r|\n/).map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
|
|
70972
|
+
/**
|
|
70428
70973
|
* Rule generator for JetBrains AI Assistant.
|
|
70429
70974
|
*
|
|
70430
70975
|
* AI Assistant reads project rules as flat Markdown files in
|
|
70431
|
-
* `.aiassistant/rules/*.md` (one file per rule, identified by filename).
|
|
70432
|
-
*
|
|
70433
|
-
*
|
|
70434
|
-
*
|
|
70435
|
-
*
|
|
70976
|
+
* `.aiassistant/rules/*.md` (one file per rule, identified by filename). There
|
|
70977
|
+
* is no special root file (unlike Junie's `guidelines.md`), so every rule —
|
|
70978
|
+
* root or non-root — is written as its own file under `rules/`.
|
|
70979
|
+
*
|
|
70980
|
+
* The rule type (Always / Manually / By model decision / By file patterns /
|
|
70981
|
+
* Off) is stored in the file itself as a leading `---` metadata block that the
|
|
70982
|
+
* IDE's "Rule type" selector edits in place. The block is not YAML: each line
|
|
70983
|
+
* is `<field>: <value>` split on the first colon, and `patterns` is a
|
|
70984
|
+
* comma-separated glob list, so it is written and parsed by hand here. A file
|
|
70985
|
+
* without an `apply:` line is treated as Off at retrieval time (it is never
|
|
70986
|
+
* attached automatically), which is why every generated file carries one.
|
|
70987
|
+
*
|
|
70988
|
+
* Field names and values come from the plugin (`AiRulesService`,
|
|
70989
|
+
* `AiRulesLLMBundle.properties`; verified on build 262.10968.75) — the docs
|
|
70990
|
+
* page describes the rule type only as an IDE setting.
|
|
70436
70991
|
*
|
|
70437
70992
|
* AI Assistant and Junie are different JetBrains products (real-time assistance
|
|
70438
70993
|
* vs. autonomous agent) with different layouts, so this is a separate target.
|
|
@@ -70440,18 +70995,154 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
70440
70995
|
* @see https://www.jetbrains.com/help/ai-assistant/configure-project-rules.html
|
|
70441
70996
|
*/
|
|
70442
70997
|
var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
70998
|
+
body;
|
|
70999
|
+
metadata;
|
|
71000
|
+
constructor({ body, metadata, ...rest }) {
|
|
71001
|
+
super({
|
|
71002
|
+
...rest,
|
|
71003
|
+
fileContent: AiassistantRule.buildFileContent({
|
|
71004
|
+
body,
|
|
71005
|
+
metadata
|
|
71006
|
+
})
|
|
71007
|
+
});
|
|
71008
|
+
this.body = body;
|
|
71009
|
+
this.metadata = metadata;
|
|
71010
|
+
}
|
|
70443
71011
|
static getSettablePaths(_options = {}) {
|
|
70444
71012
|
return { nonRoot: { relativeDirPath: AIASSISTANT_RULES_DIR_PATH } };
|
|
70445
71013
|
}
|
|
71014
|
+
getBody() {
|
|
71015
|
+
return this.body;
|
|
71016
|
+
}
|
|
71017
|
+
getMetadata() {
|
|
71018
|
+
return this.metadata;
|
|
71019
|
+
}
|
|
71020
|
+
/**
|
|
71021
|
+
* Serialize the metadata block the way the IDE writes it
|
|
71022
|
+
* (`---\n<field>: <value>\n---`), followed by the body.
|
|
71023
|
+
*/
|
|
71024
|
+
static buildFileContent({ body, metadata }) {
|
|
71025
|
+
if (metadata === void 0) return body;
|
|
71026
|
+
const lines = [`apply: ${metadata.apply}`];
|
|
71027
|
+
if (metadata.apply === "by model decision" && metadata.instructions) lines.push(`instructions: ${flattenValue(metadata.instructions)}`);
|
|
71028
|
+
if (metadata.apply === "by file patterns" && metadata.patterns && metadata.patterns.length > 0) lines.push(`patterns: ${metadata.patterns.map(flattenValue).join(", ")}`);
|
|
71029
|
+
return `${METADATA_DELIMITER}\n${lines.join("\n")}\n${METADATA_DELIMITER}\n\n${body}`;
|
|
71030
|
+
}
|
|
71031
|
+
/**
|
|
71032
|
+
* Split a rule file into its metadata block and body. The plugin locates the
|
|
71033
|
+
* block with `^---\s*([\s\S]*?)---\s*` (anchored at the very start of the
|
|
71034
|
+
* file); this reads it line by line instead — a delimiter is a line that is
|
|
71035
|
+
* exactly `---` — which is linear on any input and tolerates a BOM or blank
|
|
71036
|
+
* lines before the block. Each line is split on its first colon and trimmed;
|
|
71037
|
+
* unknown fields are ignored; a missing block or `apply:` line yields no
|
|
71038
|
+
* metadata, while an unrecognized `apply:` value is kept verbatim.
|
|
71039
|
+
*/
|
|
71040
|
+
static parseFileContent(fileContent) {
|
|
71041
|
+
const content = fileContent.replace(/^\uFEFF/, "").trimStart();
|
|
71042
|
+
const lines = content.split(LINE_SPLIT_REGEX);
|
|
71043
|
+
const closingIndex = lines[0]?.trim() === METADATA_DELIMITER ? lines.findIndex((line, index) => index > 0 && line.trim() === METADATA_DELIMITER) : -1;
|
|
71044
|
+
if (closingIndex === -1) return {
|
|
71045
|
+
metadata: void 0,
|
|
71046
|
+
body: content.trim()
|
|
71047
|
+
};
|
|
71048
|
+
const body = lines.slice(closingIndex + 1).join("").trim();
|
|
71049
|
+
const fields = /* @__PURE__ */ new Map();
|
|
71050
|
+
for (const line of lines.slice(1, closingIndex)) {
|
|
71051
|
+
const separatorIndex = line.indexOf(":");
|
|
71052
|
+
if (separatorIndex === -1) continue;
|
|
71053
|
+
fields.set(line.slice(0, separatorIndex).trim(), line.slice(separatorIndex + 1).trim());
|
|
71054
|
+
}
|
|
71055
|
+
const apply = fields.get("apply");
|
|
71056
|
+
if (apply === void 0 || apply.length === 0) return {
|
|
71057
|
+
metadata: void 0,
|
|
71058
|
+
body
|
|
71059
|
+
};
|
|
71060
|
+
const instructions = fields.get("instructions");
|
|
71061
|
+
const patterns = fields.get("patterns");
|
|
71062
|
+
return {
|
|
71063
|
+
metadata: {
|
|
71064
|
+
apply,
|
|
71065
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
71066
|
+
...apply === "by file patterns" && patterns && { patterns: splitBraceAwareList(patterns) }
|
|
71067
|
+
},
|
|
71068
|
+
body
|
|
71069
|
+
};
|
|
71070
|
+
}
|
|
71071
|
+
/**
|
|
71072
|
+
* Derive the metadata block from the canonical frontmatter, mirroring the
|
|
71073
|
+
* Cursor mapping: an explicit `aiassistant.apply` wins; otherwise specific
|
|
71074
|
+
* `globs` become `by file patterns`, a `description` on a rule with no
|
|
71075
|
+
* `globs` at all becomes the `by model decision` condition, and everything
|
|
71076
|
+
* else — the root rule, universal globs (which already say "every file",
|
|
71077
|
+
* whatever the description), bare rules — is `always`.
|
|
71078
|
+
*
|
|
71079
|
+
* The plugin splits `patterns` on every comma, so a brace alternation such
|
|
71080
|
+
* as `*.{ts,tsx}` is expanded into one glob per branch before it is written.
|
|
71081
|
+
*/
|
|
71082
|
+
static buildMetadata(frontmatter) {
|
|
71083
|
+
const globs = (frontmatter.globs ?? []).map((glob) => flattenValue(glob)).filter((glob) => glob.length > 0).flatMap((glob) => expandBraceAlternations(glob));
|
|
71084
|
+
const hasUniversalGlob = globs.some((glob) => UNIVERSAL_GLOBS$2.has(glob));
|
|
71085
|
+
const instructions = frontmatter.description && flattenValue(frontmatter.description);
|
|
71086
|
+
const explicitApply = frontmatter.aiassistant?.apply && flattenValue(frontmatter.aiassistant.apply);
|
|
71087
|
+
const apply = explicitApply ? explicitApply : frontmatter.root !== true && globs.length > 0 && !hasUniversalGlob ? "by file patterns" : frontmatter.root !== true && globs.length === 0 && instructions ? "by model decision" : "always";
|
|
71088
|
+
const patterns = globs;
|
|
71089
|
+
return {
|
|
71090
|
+
apply,
|
|
71091
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
71092
|
+
...apply === "by file patterns" && patterns.length > 0 && { patterns }
|
|
71093
|
+
};
|
|
71094
|
+
}
|
|
71095
|
+
/**
|
|
71096
|
+
* Warn, at generate time, about a block the plugin cannot act on as the
|
|
71097
|
+
* author presumably intended.
|
|
71098
|
+
*/
|
|
71099
|
+
static warnAboutMetadata({ metadata, relativeFilePath }) {
|
|
71100
|
+
const { apply, instructions, patterns = [] } = metadata;
|
|
71101
|
+
if (!isAiassistantApply(apply)) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply "${apply}" is not one of ${AIASSISTANT_APPLY_VALUES.join(", ")}; it is written as is, and the current AI Assistant treats such a rule as Off.`);
|
|
71102
|
+
if (apply === "by file patterns" && patterns.length === 0) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply is "by file patterns" but the rule has no globs, so AI Assistant will never attach it.`);
|
|
71103
|
+
if (apply === "by model decision" && !instructions) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply is "by model decision" but the rule has no description, so AI Assistant has no condition to decide on.`);
|
|
71104
|
+
const commaGlob = patterns.find((glob) => glob.includes(","));
|
|
71105
|
+
if (commaGlob !== void 0) warnWithFallback(void 0, `${relativeFilePath}: the glob "${commaGlob}" contains a comma, which AI Assistant reads as a pattern separator.`);
|
|
71106
|
+
if ([
|
|
71107
|
+
apply,
|
|
71108
|
+
instructions ?? "",
|
|
71109
|
+
...patterns
|
|
71110
|
+
].some((value) => value.includes("---"))) warnWithFallback(void 0, `${relativeFilePath}: a metadata value contains "---", which AI Assistant reads as the end of the metadata block.`);
|
|
71111
|
+
}
|
|
70446
71112
|
toRulesyncRule() {
|
|
70447
|
-
|
|
71113
|
+
const metadata = this.metadata;
|
|
71114
|
+
const globs = metadata?.apply === "by file patterns" ? metadata.patterns ?? [] : metadata?.apply === "always" ? ["**/*"] : [];
|
|
71115
|
+
const frontmatter = {
|
|
71116
|
+
root: false,
|
|
71117
|
+
targets: ["*"],
|
|
71118
|
+
description: metadata?.apply === "by model decision" ? metadata.instructions : void 0,
|
|
71119
|
+
globs
|
|
71120
|
+
};
|
|
71121
|
+
const derivedApply = metadata === void 0 ? void 0 : AiassistantRule.buildMetadata(frontmatter).apply;
|
|
71122
|
+
const apply = metadata !== void 0 && derivedApply !== metadata.apply ? metadata.apply : void 0;
|
|
71123
|
+
return new RulesyncRule({
|
|
71124
|
+
outputRoot: process.cwd(),
|
|
71125
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
71126
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
71127
|
+
frontmatter: {
|
|
71128
|
+
...frontmatter,
|
|
71129
|
+
...apply !== void 0 && { aiassistant: { apply } }
|
|
71130
|
+
},
|
|
71131
|
+
body: this.body
|
|
71132
|
+
});
|
|
70448
71133
|
}
|
|
70449
71134
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
71135
|
+
const metadata = this.buildMetadata(rulesyncRule.getFrontmatter());
|
|
71136
|
+
this.warnAboutMetadata({
|
|
71137
|
+
metadata,
|
|
71138
|
+
relativeFilePath: rulesyncRule.getRelativeFilePath()
|
|
71139
|
+
});
|
|
70450
71140
|
return new AiassistantRule({
|
|
70451
71141
|
outputRoot,
|
|
70452
71142
|
relativeDirPath: this.getSettablePaths().nonRoot.relativeDirPath,
|
|
70453
71143
|
relativeFilePath: rulesyncRule.getRelativeFilePath(),
|
|
70454
|
-
|
|
71144
|
+
body: rulesyncRule.getBody(),
|
|
71145
|
+
metadata,
|
|
70455
71146
|
validate,
|
|
70456
71147
|
root: false
|
|
70457
71148
|
});
|
|
@@ -70459,11 +71150,13 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
70459
71150
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
70460
71151
|
const relativeDirPath = this.getSettablePaths().nonRoot.relativeDirPath;
|
|
70461
71152
|
const fileContent = await readFileContent(join(outputRoot, relativeDirPath, relativeFilePath));
|
|
71153
|
+
const { metadata, body } = this.parseFileContent(fileContent);
|
|
70462
71154
|
return new AiassistantRule({
|
|
70463
71155
|
outputRoot,
|
|
70464
71156
|
relativeDirPath,
|
|
70465
71157
|
relativeFilePath,
|
|
70466
|
-
|
|
71158
|
+
body,
|
|
71159
|
+
metadata,
|
|
70467
71160
|
validate,
|
|
70468
71161
|
root: false
|
|
70469
71162
|
});
|
|
@@ -70473,7 +71166,7 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
70473
71166
|
outputRoot,
|
|
70474
71167
|
relativeDirPath,
|
|
70475
71168
|
relativeFilePath,
|
|
70476
|
-
|
|
71169
|
+
body: "",
|
|
70477
71170
|
validate: false,
|
|
70478
71171
|
root: false
|
|
70479
71172
|
});
|
|
@@ -72030,40 +72723,14 @@ const CodebuddyRuleFrontmatterSchema = z.object({
|
|
|
72030
72723
|
enabled: z.optional(z.boolean())
|
|
72031
72724
|
});
|
|
72032
72725
|
/**
|
|
72033
|
-
* Splits a scalar `paths` value on the commas that separate patterns, leaving
|
|
72034
|
-
* the commas inside a brace group alone: CodeBuddy's memory docs say "You can
|
|
72035
|
-
* also combine multiple patterns with commas" and give
|
|
72036
|
-
* `paths: {src,lib}/**\/*.ts, tests/**\/*.test.ts` as the example, where the
|
|
72037
|
-
* first comma belongs to the brace expansion and the second separates the two
|
|
72038
|
-
* globs. Only a scalar is split — the docs describe the comma form for the
|
|
72039
|
-
* string shape, and a list already separates its patterns.
|
|
72040
|
-
*/
|
|
72041
|
-
function splitCodebuddyPathsScalar(paths) {
|
|
72042
|
-
const patterns = [];
|
|
72043
|
-
let current = "";
|
|
72044
|
-
let braceDepth = 0;
|
|
72045
|
-
for (const char of paths) {
|
|
72046
|
-
if (char === "{") braceDepth += 1;
|
|
72047
|
-
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
72048
|
-
else if (char === "," && braceDepth === 0) {
|
|
72049
|
-
patterns.push(current);
|
|
72050
|
-
current = "";
|
|
72051
|
-
continue;
|
|
72052
|
-
}
|
|
72053
|
-
current += char;
|
|
72054
|
-
}
|
|
72055
|
-
patterns.push(current);
|
|
72056
|
-
return patterns.map((pattern) => pattern.trim());
|
|
72057
|
-
}
|
|
72058
|
-
/**
|
|
72059
72726
|
* Normalizes the documented `string` / `string[]` shapes of `paths` to the
|
|
72060
72727
|
* list form the rest of the adapter works with, splitting a comma-separated
|
|
72061
|
-
* scalar into its patterns (see `
|
|
72728
|
+
* scalar into its patterns (see `splitBraceAwareList`). An empty list
|
|
72062
72729
|
* and an empty string both mean "no paths".
|
|
72063
72730
|
*/
|
|
72064
72731
|
function normalizeCodebuddyPaths(paths) {
|
|
72065
72732
|
if (paths === void 0) return;
|
|
72066
|
-
const list = (typeof paths === "string" ?
|
|
72733
|
+
const list = (typeof paths === "string" ? splitBraceAwareList(paths) : paths).filter((path) => path.trim() !== "");
|
|
72067
72734
|
return list.length > 0 ? list : void 0;
|
|
72068
72735
|
}
|
|
72069
72736
|
/**
|
|
@@ -80280,4 +80947,4 @@ async function importChecksCore(params) {
|
|
|
80280
80947
|
//#endregion
|
|
80281
80948
|
export { RulesyncCommand as $, toPosixPath as $t, CLAUDECODE_MEMORIES_DIR_NAME as A, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as An, checkPathTraversal as At, RulesyncSubagent as B, stripControlCharacters as Bn, listDirectoryEntryNames as Bt, COMMANDCODE_DIR as C, RULESYNC_PERMISSIONS_SCHEMA_URL as Cn, withWarnOnceScope as Ct, CODEXCLI_DIR as D, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Dn, assertDirectoryIfExists as Dt, CODEXCLI_BASH_RULES_FILE_NAME as E, RULESYNC_SKILLS_RELATIVE_DIR_PATH as En, applyFileMode as Et, caseFoldIdentity as F, formatError as Fn, getFileSize as Ft, RulesyncRuleFrontmatterSchema as G, readFileContentOrNull as Gt, RulesyncSkill as H, stripHiddenCharacters as Hn, listSubdirectoryNames as Ht, groupSpellingsByCaseFoldedIdentity as I, truncateText as In, getHomeDirectory as It, RulesyncIgnore as J, removeFile as Jt, RulesyncPermissions as K, removeDirectory as Kt, AUGMENTCODE_DIR as L, hasDeceptiveHiddenCharacters as Ln, isFileNotFoundError as Lt, CLAUDECODE_SKILLS_DIR_PATH as M, ALL_FEATURES as Mn, directoryExists as Mt, FACTORYDROID_DIR as N, ALL_FEATURES_WITH_WILDCARD as Nn, ensureDir as Nt, CLAUDECODE_DIR as O, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as On, assertTreeContainsNoSymlinks as Ot, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as P, DEPRECATED_FEATURE_REPLACEMENTS as Pn, fileExists as Pt, parseJsonc as Q, runWithDirectoryRollback as Qt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as R, hasEnclosingMarkOutsideKeycap as Rn, isFileSystemError as Rt, QWENCODE_LOCAL_RULE_FILE_NAME as S, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Sn, resetRunWarningState as St, ChecksProcessor as T, RULESYNC_RULES_RELATIVE_DIR_PATH as Tn, ErrorCodes as Tt, RulesyncSkillFrontmatterSchema as U, pathEscapesRoot as Ut, RulesyncSubagentFrontmatterSchema as V, stripControlCharactersKeepingLineFeeds as Vn, listFilePathsRecursively as Vt, RulesyncRule as W, readFileContent as Wt, getRulesyncSourceCandidates as X, removeTempDirectory as Xt, RulesyncHooks as Y, removeFileStrict as Yt, resolveRulesyncSourceWritePath as Z, resolvePath as Zt, IgnoreProcessor as _, RULESYNC_IGNORE_RELATIVE_FILE_PATH as _n, JsonLogger as _t, getProcessorRegistryEntry as a, ToolTargetSchema as an, SHARED_USER_MANAGED_CONFIG_PATHS as at, CommandsProcessor as b, RULESYNC_MCP_SCHEMA_URL as bn, warnOnConflictingFlags as bt, RulesProcessor as c, MAX_FILE_SIZE as cn, mergeInputRootConfigs as ct, CODEBUDDY_DIR as d, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as dn, ConfigFileSchema as dt, writeFileBuffer as en, RulesyncCommandFrontmatterSchema as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_CONFIG_RELATIVE_FILE_PATH as fn, GITIGNORE_DESTINATION_KEY as ft, McpProcessor as g, RULESYNC_HOOKS_RELATIVE_FILE_PATH as gn, ConsoleLogger as gt, shortenToWidth as h, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as hn, findControlCharacter as ht, inspectInputRoots as i, PACKAGING_TOOL_TARGETS as in, loadYaml as it, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as j, parseCommaSeparatedList as jn, createTempDirectory as jt, CLAUDECODE_LOCAL_RULE_FILE_NAME as k, RULESYNC_USER_CONFIG_DIR_NAME as kn, assertWritablePathInsideRoot as kt, SubagentsProcessor as l, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ln, resolveEffectiveInputRoots as lt, displayWidthOf as m, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as mn, assertTargetsFeaturesExclusive as mt, formatSourceLoadFailure as n, ALL_TOOL_TARGETS as nn, RulesyncCheckFrontmatterSchema as nt, convertFromTool as o, CURATED_RULES_FEATURE_SUBDIR as on, SKILL_FILE_NAME as ot, ELLIPSIS_WIDTH as p, RULESYNC_CONFIG_SCHEMA_URL as pn, SourceEntrySchema as pt, RulesyncMcp as q, removeDirectoryStrict as qt, generate as r, ALL_TOOL_TARGETS_WITH_WILDCARD as rn, stringifyFrontmatter as rt, isPackagingToolTarget as s, FEATURE_SOURCE_TREE_ENTRIES as sn, ConfigResolver as st, importFromTool as t, writeFileContent as tn, RulesyncCheck as tt, SkillsProcessor as u, RULESYNC_CHECKS_RELATIVE_DIR_PATH as un, CONFLICTING_TARGET_PAIRS as ut, HooksProcessor as v, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as vn, WarningCollectingLogger as vt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as w, RULESYNC_RELATIVE_DIR_PATH as wn, CLIError as wt, QWENCODE_DIR as x, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as xn, withFallbackLoggerTarget as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_MCP_RELATIVE_FILE_PATH as yn, fallbackLogger as yt, getLocalSkillDirNames as z, quoteForLog as zn, isSymlink as zt };
|
|
80282
80949
|
|
|
80283
|
-
//# sourceMappingURL=import-
|
|
80950
|
+
//# sourceMappingURL=import-BiyWNVZY.js.map
|