rulesync 16.36.0 → 16.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-D_iukHSy.js → import-BnF2J75x.js} +364 -55
- package/dist/import-BnF2J75x.js.map +1 -0
- package/dist/{import-D_daTMgR.cjs → import-xo5me5dm.cjs} +363 -54
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +4 -0
- 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
|
@@ -8445,6 +8445,7 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
8445
8445
|
trigger: zod_mini.z.optional(zod_mini.z.string()),
|
|
8446
8446
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8447
8447
|
})),
|
|
8448
|
+
aiassistant: zod_mini.z.optional(zod_mini.z.looseObject({ apply: zod_mini.z.optional(zod_mini.z.string()) })),
|
|
8448
8449
|
devin: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
8449
8450
|
trigger: zod_mini.z.optional(zod_mini.z.string()),
|
|
8450
8451
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -9669,6 +9670,8 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
9669
9670
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
9670
9671
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9671
9672
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9673
|
+
icon: zod_mini.z.optional(zod_mini.z.string()),
|
|
9674
|
+
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
9672
9675
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
9673
9676
|
})),
|
|
9674
9677
|
factorydroid: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -23683,14 +23686,23 @@ var ContinueHooks = class extends SettingsJsonHooks {
|
|
|
23683
23686
|
* portable `command` field is written, which upstream copies to both when
|
|
23684
23687
|
* neither is present. Note the cloud agent runs hooks in a Linux sandbox and
|
|
23685
23688
|
* honors only `bash` and `command` — a `powershell` entry is ignored there.
|
|
23689
|
+
* VS Code reads the same file and maps `bash` to `osx` / `linux` and
|
|
23690
|
+
* `powershell` to `windows`; it also documents its own per-OS overrides
|
|
23691
|
+
* (`windows`, `linux`, `osx`) next to `command`, which are typed here so an
|
|
23692
|
+
* entry that uses them instead of a shell field is not mistaken for one
|
|
23693
|
+
* without a command.
|
|
23686
23694
|
*
|
|
23687
23695
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23696
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23688
23697
|
*/
|
|
23689
23698
|
const CopilotHookEntrySchema = zod_mini.z.looseObject({
|
|
23690
23699
|
type: zod_mini.z.string(),
|
|
23691
23700
|
bash: zod_mini.z.optional(zod_mini.z.string()),
|
|
23692
23701
|
powershell: zod_mini.z.optional(zod_mini.z.string()),
|
|
23693
23702
|
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
23703
|
+
windows: zod_mini.z.optional(zod_mini.z.string()),
|
|
23704
|
+
linux: zod_mini.z.optional(zod_mini.z.string()),
|
|
23705
|
+
osx: zod_mini.z.optional(zod_mini.z.string()),
|
|
23694
23706
|
cwd: zod_mini.z.optional(zod_mini.z.string()),
|
|
23695
23707
|
env: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
23696
23708
|
timeoutSec: zod_mini.z.optional(zod_mini.z.number()),
|
|
@@ -23781,17 +23793,30 @@ function resolveImportCommand$1(entry, logger) {
|
|
|
23781
23793
|
*
|
|
23782
23794
|
* Generate re-emits any non-canonical key verbatim through `rest`, so a key
|
|
23783
23795
|
* dropped here does not survive an import → generate round trip. `cwd` is a
|
|
23784
|
-
* documented Copilot hook field and was previously lost that way
|
|
23796
|
+
* documented Copilot hook field and was previously lost that way, as were the
|
|
23797
|
+
* VS Code per-OS overrides `windows` / `linux` / `osx`: an entry authored with
|
|
23798
|
+
* only those (no `command`) imported as a hook with no command at all and was
|
|
23799
|
+
* regenerated empty.
|
|
23785
23800
|
*
|
|
23786
23801
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
23787
23802
|
* @see https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks
|
|
23803
|
+
* @see https://code.visualstudio.com/docs/agents/reference/hooks-reference
|
|
23788
23804
|
*/
|
|
23789
23805
|
function importPassthrough$1(entry) {
|
|
23790
23806
|
const passthrough = {};
|
|
23791
23807
|
if (entry.cwd !== void 0) passthrough.cwd = entry.cwd;
|
|
23792
23808
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
23809
|
+
for (const key of OS_OVERRIDE_KEYS) if (entry[key] !== void 0) passthrough[key] = entry[key];
|
|
23793
23810
|
return passthrough;
|
|
23794
23811
|
}
|
|
23812
|
+
const OS_OVERRIDE_KEYS = [
|
|
23813
|
+
"windows",
|
|
23814
|
+
"linux",
|
|
23815
|
+
"osx"
|
|
23816
|
+
];
|
|
23817
|
+
function hasOsOverride(passthrough) {
|
|
23818
|
+
return OS_OVERRIDE_KEYS.some((key) => passthrough[key] !== void 0);
|
|
23819
|
+
}
|
|
23795
23820
|
/**
|
|
23796
23821
|
* Extract hooks from Copilot hooks JSON into canonical format.
|
|
23797
23822
|
* Copilot format: { version: 1, hooks: { eventName: [...hookEntries] } }
|
|
@@ -23812,12 +23837,14 @@ function copilotHooksToCanonical(copilotHooks, logger) {
|
|
|
23812
23837
|
const entry = parseResult.data;
|
|
23813
23838
|
const { command, shell } = resolveImportCommand$1(entry, logger);
|
|
23814
23839
|
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
23840
|
+
const passthrough = importPassthrough$1(entry);
|
|
23841
|
+
if (command === void 0 && hasOsOverride(passthrough)) logger?.warn(`Copilot hook on '${copilotEventName}' has only VS Code per-OS overrides (windows/linux/osx) and no portable command; it is preserved for copilot, but other targets will generate it without a command until a \`command\` is added.`);
|
|
23815
23842
|
defs.push({
|
|
23816
23843
|
type: "command",
|
|
23817
23844
|
...command !== void 0 && { command },
|
|
23818
23845
|
...shell !== void 0 && { shell },
|
|
23819
23846
|
...timeout !== void 0 && { timeout },
|
|
23820
|
-
...
|
|
23847
|
+
...passthrough
|
|
23821
23848
|
});
|
|
23822
23849
|
}
|
|
23823
23850
|
if (defs.length > 0) canonical[eventName] = defs;
|
|
@@ -23980,6 +24007,8 @@ const CopilotCliHookEntrySchema = zod_mini.z.looseObject({
|
|
|
23980
24007
|
bash: zod_mini.z.optional(zod_mini.z.string()),
|
|
23981
24008
|
powershell: zod_mini.z.optional(zod_mini.z.string()),
|
|
23982
24009
|
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
24010
|
+
exec: zod_mini.z.optional(zod_mini.z.string()),
|
|
24011
|
+
args: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
23983
24012
|
prompt: zod_mini.z.optional(zod_mini.z.string()),
|
|
23984
24013
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
23985
24014
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
@@ -24009,6 +24038,32 @@ function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger
|
|
|
24009
24038
|
return {};
|
|
24010
24039
|
}
|
|
24011
24040
|
/**
|
|
24041
|
+
* Pick the command fields of an exported `command` entry.
|
|
24042
|
+
*
|
|
24043
|
+
* A canonical `args` list (an empty one counts) selects the CLI's shell-free
|
|
24044
|
+
* form: `command` becomes `exec` and the list is written as `args`, so the
|
|
24045
|
+
* executable is spawned directly. The `shell` selector is meaningless there
|
|
24046
|
+
* and is dropped with a warning. Otherwise the canonical `shell` selector
|
|
24047
|
+
* picks the shell-specific field; without it the portable `command` field is
|
|
24048
|
+
* written, which upstream copies to both `bash` and `powershell`. Keying off
|
|
24049
|
+
* `shell` rather than `process.platform` keeps the generated file identical
|
|
24050
|
+
* on every machine.
|
|
24051
|
+
*/
|
|
24052
|
+
function resolveExportCommandPart({ def, eventName, logger }) {
|
|
24053
|
+
if (Array.isArray(def.args)) {
|
|
24054
|
+
if (def.shell !== void 0) logger?.warn(`Copilot CLI runs an \`exec\` hook without a shell; dropping shell "${def.shell}" on '${eventName}'.`);
|
|
24055
|
+
return compact({
|
|
24056
|
+
exec: def.command,
|
|
24057
|
+
args: def.args,
|
|
24058
|
+
env: def.env
|
|
24059
|
+
});
|
|
24060
|
+
}
|
|
24061
|
+
return compact({
|
|
24062
|
+
[def.shell ?? "command"]: def.command,
|
|
24063
|
+
env: def.env
|
|
24064
|
+
});
|
|
24065
|
+
}
|
|
24066
|
+
/**
|
|
24012
24067
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
24013
24068
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
24014
24069
|
*/
|
|
@@ -24048,19 +24103,17 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
24048
24103
|
...timeoutPart,
|
|
24049
24104
|
...rest
|
|
24050
24105
|
});
|
|
24051
|
-
else if (hookType === "command") {
|
|
24052
|
-
|
|
24053
|
-
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
});
|
|
24063
|
-
}
|
|
24106
|
+
else if (hookType === "command") entries.push({
|
|
24107
|
+
type: "command",
|
|
24108
|
+
...matcherPart,
|
|
24109
|
+
...resolveExportCommandPart({
|
|
24110
|
+
def,
|
|
24111
|
+
eventName,
|
|
24112
|
+
logger
|
|
24113
|
+
}),
|
|
24114
|
+
...timeoutPart,
|
|
24115
|
+
...rest
|
|
24116
|
+
});
|
|
24064
24117
|
}
|
|
24065
24118
|
return entries;
|
|
24066
24119
|
}
|
|
@@ -24100,6 +24153,10 @@ function importPassthrough(entry) {
|
|
|
24100
24153
|
/**
|
|
24101
24154
|
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
24102
24155
|
*
|
|
24156
|
+
* The CLI-only `exec` form maps to canonical `command` + `args`, the exec form
|
|
24157
|
+
* shared with AugmentCode and Claude Code, so it no longer imports as a hook
|
|
24158
|
+
* with no command at all.
|
|
24159
|
+
*
|
|
24103
24160
|
* A shell-specific field carries its `shell` through so re-export writes the
|
|
24104
24161
|
* same field back. An entry using only the portable `command` field leaves
|
|
24105
24162
|
* `shell` unset, which re-export renders as the portable field again.
|
|
@@ -24113,6 +24170,13 @@ function importPassthrough(entry) {
|
|
|
24113
24170
|
function resolveImportCommand(entry, logger) {
|
|
24114
24171
|
const hasBash = typeof entry.bash === "string";
|
|
24115
24172
|
const hasPowershell = typeof entry.powershell === "string";
|
|
24173
|
+
if (typeof entry.exec === "string") {
|
|
24174
|
+
if (hasBash || hasPowershell || typeof entry.command === "string") logger?.warn("Copilot CLI hook has both exec and a shell command; using exec and ignoring the shell fields, as the hooks reference says not to combine them.");
|
|
24175
|
+
return {
|
|
24176
|
+
command: entry.exec,
|
|
24177
|
+
args: entry.args ?? []
|
|
24178
|
+
};
|
|
24179
|
+
}
|
|
24116
24180
|
if (hasBash && hasPowershell) {
|
|
24117
24181
|
logger?.warn("Copilot CLI hook has both bash and powershell commands; using bash and ignoring powershell, so the imported config does not depend on the machine the import ran on.");
|
|
24118
24182
|
return {
|
|
@@ -24162,11 +24226,12 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
24162
24226
|
...passthrough
|
|
24163
24227
|
});
|
|
24164
24228
|
else {
|
|
24165
|
-
const { command, shell } = resolveImportCommand(entry, logger);
|
|
24229
|
+
const { command, shell, args } = resolveImportCommand(entry, logger);
|
|
24166
24230
|
defs.push({
|
|
24167
24231
|
type: "command",
|
|
24168
24232
|
...command !== void 0 && { command },
|
|
24169
24233
|
...shell !== void 0 && { shell },
|
|
24234
|
+
...args !== void 0 && { args },
|
|
24170
24235
|
...matcherPart,
|
|
24171
24236
|
...timeoutPart,
|
|
24172
24237
|
...passthrough
|
|
@@ -24716,6 +24781,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
24716
24781
|
...def.loop_limit !== void 0 && { loop_limit: def.loop_limit },
|
|
24717
24782
|
...def.matcher !== void 0 && def.matcher !== null && { matcher: def.matcher },
|
|
24718
24783
|
...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
|
|
24784
|
+
...def.type === "prompt" && def.model !== void 0 && def.model !== null && { model: def.model },
|
|
24719
24785
|
...def.failClosed !== void 0 && def.failClosed !== null && { failClosed: def.failClosed }
|
|
24720
24786
|
}));
|
|
24721
24787
|
if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
|
|
@@ -59200,8 +59266,11 @@ const CursorSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
59200
59266
|
name: zod_mini.z.string(),
|
|
59201
59267
|
description: zod_mini.z.string(),
|
|
59202
59268
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
59269
|
+
globs: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
59203
59270
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
59204
59271
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
59272
|
+
icon: zod_mini.z.optional(zod_mini.z.string()),
|
|
59273
|
+
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
59205
59274
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
59206
59275
|
});
|
|
59207
59276
|
/**
|
|
@@ -59253,10 +59322,13 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
59253
59322
|
}
|
|
59254
59323
|
toRulesyncSkill() {
|
|
59255
59324
|
const frontmatter = this.getFrontmatter();
|
|
59325
|
+
const paths = frontmatter.paths ?? frontmatter.globs;
|
|
59256
59326
|
const cursorSection = {
|
|
59257
|
-
...
|
|
59327
|
+
...paths !== void 0 && { paths },
|
|
59258
59328
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
59259
59329
|
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
59330
|
+
...frontmatter.icon !== void 0 && { icon: frontmatter.icon },
|
|
59331
|
+
...frontmatter.color !== void 0 && { color: frontmatter.color },
|
|
59260
59332
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
59261
59333
|
};
|
|
59262
59334
|
const rulesyncFrontmatter = {
|
|
@@ -59298,6 +59370,8 @@ var CursorSkill = class CursorSkill extends ToolSkill {
|
|
|
59298
59370
|
...cursorSection?.paths !== void 0 && { paths: cursorSection.paths },
|
|
59299
59371
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
|
|
59300
59372
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
59373
|
+
...cursorSection?.icon !== void 0 && { icon: cursorSection.icon },
|
|
59374
|
+
...cursorSection?.color !== void 0 && { color: cursorSection.color },
|
|
59301
59375
|
...metadata !== void 0 && { metadata }
|
|
59302
59376
|
};
|
|
59303
59377
|
return new CursorSkill({
|
|
@@ -66057,11 +66131,17 @@ var CopilotSubagent = class CopilotSubagent extends ToolSubagent {
|
|
|
66057
66131
|
*
|
|
66058
66132
|
* Reference: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli
|
|
66059
66133
|
* https://docs.github.com/en/copilot/reference/custom-agents-configuration
|
|
66134
|
+
* https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference
|
|
66060
66135
|
*
|
|
66061
66136
|
* Per the configuration docs:
|
|
66062
66137
|
* - `description` is required.
|
|
66063
66138
|
* - `name`, `target`, `tools`, `model`, `disable-model-invocation`,
|
|
66064
66139
|
* `user-invocable`, `mcp-servers`, and `metadata` are optional.
|
|
66140
|
+
* - The CLI command reference adds `models` (authored models in priority
|
|
66141
|
+
* order, overriding `model` when both are set) and `modelPolicy`
|
|
66142
|
+
* (`preferred` lets a `subagents` settings override or the `/subagents`
|
|
66143
|
+
* picker replace the model; `required` locks dispatch to the authored
|
|
66144
|
+
* list). CLI v1.0.81+; the shared reference page still lists `model` only.
|
|
66065
66145
|
*
|
|
66066
66146
|
* `looseObject` is used so that future additional fields are passed through
|
|
66067
66147
|
* unchanged (consistent with the rest of rulesync's frontmatter schemas).
|
|
@@ -66072,6 +66152,8 @@ const CopilotCliSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
66072
66152
|
target: zod_mini.z.optional(zod_mini.z.string()),
|
|
66073
66153
|
tools: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
66074
66154
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
66155
|
+
models: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
66156
|
+
modelPolicy: zod_mini.z.optional(zod_mini.z.enum(["preferred", "required"])),
|
|
66075
66157
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
66076
66158
|
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
66077
66159
|
"mcp-servers": zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.unknown())),
|
|
@@ -70448,16 +70530,131 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
70448
70530
|
}
|
|
70449
70531
|
};
|
|
70450
70532
|
//#endregion
|
|
70533
|
+
//#region src/utils/brace-aware-list.ts
|
|
70534
|
+
/**
|
|
70535
|
+
* Splits a comma-separated pattern list on the commas that separate its
|
|
70536
|
+
* entries, leaving the commas inside a brace group alone: in
|
|
70537
|
+
* `{src,lib}/**\/*.ts, tests/**\/*.test.ts` the first comma belongs to the
|
|
70538
|
+
* brace expansion and the second separates the two globs. Entries are trimmed;
|
|
70539
|
+
* empty entries are dropped.
|
|
70540
|
+
*
|
|
70541
|
+
* @example
|
|
70542
|
+
* splitBraceAwareList("src/**\/*.{ts,tsx}, docs/**")
|
|
70543
|
+
* // => ["src/**\/*.{ts,tsx}", "docs/**"]
|
|
70544
|
+
*/
|
|
70545
|
+
const splitBraceAwareList = (value) => {
|
|
70546
|
+
const entries = [];
|
|
70547
|
+
let current = "";
|
|
70548
|
+
let braceDepth = 0;
|
|
70549
|
+
for (const char of value) {
|
|
70550
|
+
if (char === "{") braceDepth += 1;
|
|
70551
|
+
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
70552
|
+
else if (char === "," && braceDepth === 0) {
|
|
70553
|
+
entries.push(current);
|
|
70554
|
+
current = "";
|
|
70555
|
+
continue;
|
|
70556
|
+
}
|
|
70557
|
+
current += char;
|
|
70558
|
+
}
|
|
70559
|
+
entries.push(current);
|
|
70560
|
+
return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
70561
|
+
};
|
|
70562
|
+
/**
|
|
70563
|
+
* Upper bound on the patterns one glob may expand to; past it the glob is
|
|
70564
|
+
* returned verbatim, since `{a,b}` repeated k times yields 2^k branches.
|
|
70565
|
+
*/
|
|
70566
|
+
const MAX_BRACE_EXPANSIONS = 256;
|
|
70567
|
+
/** Matches an innermost brace group with at least one comma: `{a,b}`, not `{a}` or `{a,{b,c}}`. */
|
|
70568
|
+
const INNERMOST_BRACE_GROUP_REGEX = /\{([^{},]*(?:,[^{},]*)+)\}/;
|
|
70569
|
+
/**
|
|
70570
|
+
* Expands the comma alternations of a glob (`a.{ts,tsx}` → `a.ts`, `a.tsx`)
|
|
70571
|
+
* into one pattern per combination, innermost group first, so the result can
|
|
70572
|
+
* be handed to a consumer that splits its pattern list on bare commas. A glob
|
|
70573
|
+
* without a brace alternation is returned as is; a brace group without a
|
|
70574
|
+
* comma (e.g. `{a}`) is left untouched. A glob whose expansion would exceed
|
|
70575
|
+
* `MAX_BRACE_EXPANSIONS` patterns is returned as is as well.
|
|
70576
|
+
*
|
|
70577
|
+
* The cap is enforced on the work list itself rather than by counting the
|
|
70578
|
+
* groups up front: a nested group such as `{{a,b},c}` is rewritten into one
|
|
70579
|
+
* new group per inner alternative, so the number of patterns in flight is the
|
|
70580
|
+
* only bound that holds for arbitrary nesting.
|
|
70581
|
+
*
|
|
70582
|
+
* @example
|
|
70583
|
+
* expandBraceAlternations("src/**\/*.{ts,tsx}")
|
|
70584
|
+
* // => ["src/**\/*.ts", "src/**\/*.tsx"]
|
|
70585
|
+
*/
|
|
70586
|
+
const expandBraceAlternations = (glob) => {
|
|
70587
|
+
let pending = [glob];
|
|
70588
|
+
for (;;) {
|
|
70589
|
+
const next = [];
|
|
70590
|
+
let expanded = false;
|
|
70591
|
+
for (const pattern of pending) {
|
|
70592
|
+
const match = INNERMOST_BRACE_GROUP_REGEX.exec(pattern);
|
|
70593
|
+
if (match === null) {
|
|
70594
|
+
next.push(pattern);
|
|
70595
|
+
continue;
|
|
70596
|
+
}
|
|
70597
|
+
expanded = true;
|
|
70598
|
+
const prefix = pattern.slice(0, match.index);
|
|
70599
|
+
const suffix = pattern.slice(match.index + match[0].length);
|
|
70600
|
+
for (const alternative of (match[1] ?? "").split(",")) next.push(`${prefix}${alternative}${suffix}`);
|
|
70601
|
+
if (next.length > MAX_BRACE_EXPANSIONS) return [glob];
|
|
70602
|
+
}
|
|
70603
|
+
if (!expanded) return [...new Set(next)];
|
|
70604
|
+
pending = next;
|
|
70605
|
+
}
|
|
70606
|
+
};
|
|
70607
|
+
//#endregion
|
|
70451
70608
|
//#region src/features/rules/aiassistant-rule.ts
|
|
70452
70609
|
/**
|
|
70610
|
+
* Rule types AI Assistant recognizes on the `apply:` metadata line. The values
|
|
70611
|
+
* are matched case-sensitively by the plugin, so they are emitted verbatim.
|
|
70612
|
+
* Any other value is carried through untouched (the plugin treats it as Off
|
|
70613
|
+
* today; a future build may recognize it).
|
|
70614
|
+
*/
|
|
70615
|
+
const AIASSISTANT_APPLY_VALUES = [
|
|
70616
|
+
"always",
|
|
70617
|
+
"manually",
|
|
70618
|
+
"by model decision",
|
|
70619
|
+
"by file patterns",
|
|
70620
|
+
"off"
|
|
70621
|
+
];
|
|
70622
|
+
const isAiassistantApply = (value) => AIASSISTANT_APPLY_VALUES.includes(value);
|
|
70623
|
+
/**
|
|
70624
|
+
* Globs that match the whole project and so say nothing beyond "always".
|
|
70625
|
+
*/
|
|
70626
|
+
const UNIVERSAL_GLOBS$2 = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
70627
|
+
const METADATA_DELIMITER = "---";
|
|
70628
|
+
/**
|
|
70629
|
+
* Splits text into lines that keep their own line terminator, so the body can
|
|
70630
|
+
* be sliced back out of the file byte-for-byte. Lone `\r` counts as a break,
|
|
70631
|
+
* matching Kotlin's `lines()`, which the plugin uses on the block.
|
|
70632
|
+
*/
|
|
70633
|
+
const LINE_SPLIT_REGEX = /(?<=\r\n|\r(?!\n)|(?<!\r)\n)/;
|
|
70634
|
+
/**
|
|
70635
|
+
* The block is line-based, so a value must not carry a line break: one would
|
|
70636
|
+
* start a new `<field>: <value>` line and could override `apply:`.
|
|
70637
|
+
*/
|
|
70638
|
+
const flattenValue = (value) => value.split(/\r\n|\r|\n/).map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
|
|
70639
|
+
/**
|
|
70453
70640
|
* Rule generator for JetBrains AI Assistant.
|
|
70454
70641
|
*
|
|
70455
70642
|
* AI Assistant reads project rules as flat Markdown files in
|
|
70456
|
-
* `.aiassistant/rules/*.md` (one file per rule, identified by filename).
|
|
70457
|
-
*
|
|
70458
|
-
*
|
|
70459
|
-
*
|
|
70460
|
-
*
|
|
70643
|
+
* `.aiassistant/rules/*.md` (one file per rule, identified by filename). There
|
|
70644
|
+
* is no special root file (unlike Junie's `guidelines.md`), so every rule —
|
|
70645
|
+
* root or non-root — is written as its own file under `rules/`.
|
|
70646
|
+
*
|
|
70647
|
+
* The rule type (Always / Manually / By model decision / By file patterns /
|
|
70648
|
+
* Off) is stored in the file itself as a leading `---` metadata block that the
|
|
70649
|
+
* IDE's "Rule type" selector edits in place. The block is not YAML: each line
|
|
70650
|
+
* is `<field>: <value>` split on the first colon, and `patterns` is a
|
|
70651
|
+
* comma-separated glob list, so it is written and parsed by hand here. A file
|
|
70652
|
+
* without an `apply:` line is treated as Off at retrieval time (it is never
|
|
70653
|
+
* attached automatically), which is why every generated file carries one.
|
|
70654
|
+
*
|
|
70655
|
+
* Field names and values come from the plugin (`AiRulesService`,
|
|
70656
|
+
* `AiRulesLLMBundle.properties`; verified on build 262.10968.75) — the docs
|
|
70657
|
+
* page describes the rule type only as an IDE setting.
|
|
70461
70658
|
*
|
|
70462
70659
|
* AI Assistant and Junie are different JetBrains products (real-time assistance
|
|
70463
70660
|
* vs. autonomous agent) with different layouts, so this is a separate target.
|
|
@@ -70465,18 +70662,154 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
70465
70662
|
* @see https://www.jetbrains.com/help/ai-assistant/configure-project-rules.html
|
|
70466
70663
|
*/
|
|
70467
70664
|
var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
70665
|
+
body;
|
|
70666
|
+
metadata;
|
|
70667
|
+
constructor({ body, metadata, ...rest }) {
|
|
70668
|
+
super({
|
|
70669
|
+
...rest,
|
|
70670
|
+
fileContent: AiassistantRule.buildFileContent({
|
|
70671
|
+
body,
|
|
70672
|
+
metadata
|
|
70673
|
+
})
|
|
70674
|
+
});
|
|
70675
|
+
this.body = body;
|
|
70676
|
+
this.metadata = metadata;
|
|
70677
|
+
}
|
|
70468
70678
|
static getSettablePaths(_options = {}) {
|
|
70469
70679
|
return { nonRoot: { relativeDirPath: AIASSISTANT_RULES_DIR_PATH } };
|
|
70470
70680
|
}
|
|
70681
|
+
getBody() {
|
|
70682
|
+
return this.body;
|
|
70683
|
+
}
|
|
70684
|
+
getMetadata() {
|
|
70685
|
+
return this.metadata;
|
|
70686
|
+
}
|
|
70687
|
+
/**
|
|
70688
|
+
* Serialize the metadata block the way the IDE writes it
|
|
70689
|
+
* (`---\n<field>: <value>\n---`), followed by the body.
|
|
70690
|
+
*/
|
|
70691
|
+
static buildFileContent({ body, metadata }) {
|
|
70692
|
+
if (metadata === void 0) return body;
|
|
70693
|
+
const lines = [`apply: ${metadata.apply}`];
|
|
70694
|
+
if (metadata.apply === "by model decision" && metadata.instructions) lines.push(`instructions: ${flattenValue(metadata.instructions)}`);
|
|
70695
|
+
if (metadata.apply === "by file patterns" && metadata.patterns && metadata.patterns.length > 0) lines.push(`patterns: ${metadata.patterns.map(flattenValue).join(", ")}`);
|
|
70696
|
+
return `${METADATA_DELIMITER}\n${lines.join("\n")}\n${METADATA_DELIMITER}\n\n${body}`;
|
|
70697
|
+
}
|
|
70698
|
+
/**
|
|
70699
|
+
* Split a rule file into its metadata block and body. The plugin locates the
|
|
70700
|
+
* block with `^---\s*([\s\S]*?)---\s*` (anchored at the very start of the
|
|
70701
|
+
* file); this reads it line by line instead — a delimiter is a line that is
|
|
70702
|
+
* exactly `---` — which is linear on any input and tolerates a BOM or blank
|
|
70703
|
+
* lines before the block. Each line is split on its first colon and trimmed;
|
|
70704
|
+
* unknown fields are ignored; a missing block or `apply:` line yields no
|
|
70705
|
+
* metadata, while an unrecognized `apply:` value is kept verbatim.
|
|
70706
|
+
*/
|
|
70707
|
+
static parseFileContent(fileContent) {
|
|
70708
|
+
const content = fileContent.replace(/^\uFEFF/, "").trimStart();
|
|
70709
|
+
const lines = content.split(LINE_SPLIT_REGEX);
|
|
70710
|
+
const closingIndex = lines[0]?.trim() === METADATA_DELIMITER ? lines.findIndex((line, index) => index > 0 && line.trim() === METADATA_DELIMITER) : -1;
|
|
70711
|
+
if (closingIndex === -1) return {
|
|
70712
|
+
metadata: void 0,
|
|
70713
|
+
body: content.trim()
|
|
70714
|
+
};
|
|
70715
|
+
const body = lines.slice(closingIndex + 1).join("").trim();
|
|
70716
|
+
const fields = /* @__PURE__ */ new Map();
|
|
70717
|
+
for (const line of lines.slice(1, closingIndex)) {
|
|
70718
|
+
const separatorIndex = line.indexOf(":");
|
|
70719
|
+
if (separatorIndex === -1) continue;
|
|
70720
|
+
fields.set(line.slice(0, separatorIndex).trim(), line.slice(separatorIndex + 1).trim());
|
|
70721
|
+
}
|
|
70722
|
+
const apply = fields.get("apply");
|
|
70723
|
+
if (apply === void 0 || apply.length === 0) return {
|
|
70724
|
+
metadata: void 0,
|
|
70725
|
+
body
|
|
70726
|
+
};
|
|
70727
|
+
const instructions = fields.get("instructions");
|
|
70728
|
+
const patterns = fields.get("patterns");
|
|
70729
|
+
return {
|
|
70730
|
+
metadata: {
|
|
70731
|
+
apply,
|
|
70732
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
70733
|
+
...apply === "by file patterns" && patterns && { patterns: splitBraceAwareList(patterns) }
|
|
70734
|
+
},
|
|
70735
|
+
body
|
|
70736
|
+
};
|
|
70737
|
+
}
|
|
70738
|
+
/**
|
|
70739
|
+
* Derive the metadata block from the canonical frontmatter, mirroring the
|
|
70740
|
+
* Cursor mapping: an explicit `aiassistant.apply` wins; otherwise specific
|
|
70741
|
+
* `globs` become `by file patterns`, a `description` on a rule with no
|
|
70742
|
+
* `globs` at all becomes the `by model decision` condition, and everything
|
|
70743
|
+
* else — the root rule, universal globs (which already say "every file",
|
|
70744
|
+
* whatever the description), bare rules — is `always`.
|
|
70745
|
+
*
|
|
70746
|
+
* The plugin splits `patterns` on every comma, so a brace alternation such
|
|
70747
|
+
* as `*.{ts,tsx}` is expanded into one glob per branch before it is written.
|
|
70748
|
+
*/
|
|
70749
|
+
static buildMetadata(frontmatter) {
|
|
70750
|
+
const globs = (frontmatter.globs ?? []).map((glob) => flattenValue(glob)).filter((glob) => glob.length > 0).flatMap((glob) => expandBraceAlternations(glob));
|
|
70751
|
+
const hasUniversalGlob = globs.some((glob) => UNIVERSAL_GLOBS$2.has(glob));
|
|
70752
|
+
const instructions = frontmatter.description && flattenValue(frontmatter.description);
|
|
70753
|
+
const explicitApply = frontmatter.aiassistant?.apply && flattenValue(frontmatter.aiassistant.apply);
|
|
70754
|
+
const apply = explicitApply ? explicitApply : frontmatter.root !== true && globs.length > 0 && !hasUniversalGlob ? "by file patterns" : frontmatter.root !== true && globs.length === 0 && instructions ? "by model decision" : "always";
|
|
70755
|
+
const patterns = globs;
|
|
70756
|
+
return {
|
|
70757
|
+
apply,
|
|
70758
|
+
...apply === "by model decision" && instructions && { instructions },
|
|
70759
|
+
...apply === "by file patterns" && patterns.length > 0 && { patterns }
|
|
70760
|
+
};
|
|
70761
|
+
}
|
|
70762
|
+
/**
|
|
70763
|
+
* Warn, at generate time, about a block the plugin cannot act on as the
|
|
70764
|
+
* author presumably intended.
|
|
70765
|
+
*/
|
|
70766
|
+
static warnAboutMetadata({ metadata, relativeFilePath }) {
|
|
70767
|
+
const { apply, instructions, patterns = [] } = metadata;
|
|
70768
|
+
if (!isAiassistantApply(apply)) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply "${apply}" is not one of ${AIASSISTANT_APPLY_VALUES.join(", ")}; it is written as is, and the current AI Assistant treats such a rule as Off.`);
|
|
70769
|
+
if (apply === "by file patterns" && patterns.length === 0) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply is "by file patterns" but the rule has no globs, so AI Assistant will never attach it.`);
|
|
70770
|
+
if (apply === "by model decision" && !instructions) warnWithFallback(void 0, `${relativeFilePath}: aiassistant.apply is "by model decision" but the rule has no description, so AI Assistant has no condition to decide on.`);
|
|
70771
|
+
const commaGlob = patterns.find((glob) => glob.includes(","));
|
|
70772
|
+
if (commaGlob !== void 0) warnWithFallback(void 0, `${relativeFilePath}: the glob "${commaGlob}" contains a comma, which AI Assistant reads as a pattern separator.`);
|
|
70773
|
+
if ([
|
|
70774
|
+
apply,
|
|
70775
|
+
instructions ?? "",
|
|
70776
|
+
...patterns
|
|
70777
|
+
].some((value) => value.includes("---"))) warnWithFallback(void 0, `${relativeFilePath}: a metadata value contains "---", which AI Assistant reads as the end of the metadata block.`);
|
|
70778
|
+
}
|
|
70471
70779
|
toRulesyncRule() {
|
|
70472
|
-
|
|
70780
|
+
const metadata = this.metadata;
|
|
70781
|
+
const globs = metadata?.apply === "by file patterns" ? metadata.patterns ?? [] : metadata?.apply === "always" ? ["**/*"] : [];
|
|
70782
|
+
const frontmatter = {
|
|
70783
|
+
root: false,
|
|
70784
|
+
targets: ["*"],
|
|
70785
|
+
description: metadata?.apply === "by model decision" ? metadata.instructions : void 0,
|
|
70786
|
+
globs
|
|
70787
|
+
};
|
|
70788
|
+
const derivedApply = metadata === void 0 ? void 0 : AiassistantRule.buildMetadata(frontmatter).apply;
|
|
70789
|
+
const apply = metadata !== void 0 && derivedApply !== metadata.apply ? metadata.apply : void 0;
|
|
70790
|
+
return new RulesyncRule({
|
|
70791
|
+
outputRoot: process.cwd(),
|
|
70792
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
70793
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
70794
|
+
frontmatter: {
|
|
70795
|
+
...frontmatter,
|
|
70796
|
+
...apply !== void 0 && { aiassistant: { apply } }
|
|
70797
|
+
},
|
|
70798
|
+
body: this.body
|
|
70799
|
+
});
|
|
70473
70800
|
}
|
|
70474
70801
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
70802
|
+
const metadata = this.buildMetadata(rulesyncRule.getFrontmatter());
|
|
70803
|
+
this.warnAboutMetadata({
|
|
70804
|
+
metadata,
|
|
70805
|
+
relativeFilePath: rulesyncRule.getRelativeFilePath()
|
|
70806
|
+
});
|
|
70475
70807
|
return new AiassistantRule({
|
|
70476
70808
|
outputRoot,
|
|
70477
70809
|
relativeDirPath: this.getSettablePaths().nonRoot.relativeDirPath,
|
|
70478
70810
|
relativeFilePath: rulesyncRule.getRelativeFilePath(),
|
|
70479
|
-
|
|
70811
|
+
body: rulesyncRule.getBody(),
|
|
70812
|
+
metadata,
|
|
70480
70813
|
validate,
|
|
70481
70814
|
root: false
|
|
70482
70815
|
});
|
|
@@ -70484,11 +70817,13 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
70484
70817
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
70485
70818
|
const relativeDirPath = this.getSettablePaths().nonRoot.relativeDirPath;
|
|
70486
70819
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
70820
|
+
const { metadata, body } = this.parseFileContent(fileContent);
|
|
70487
70821
|
return new AiassistantRule({
|
|
70488
70822
|
outputRoot,
|
|
70489
70823
|
relativeDirPath,
|
|
70490
70824
|
relativeFilePath,
|
|
70491
|
-
|
|
70825
|
+
body,
|
|
70826
|
+
metadata,
|
|
70492
70827
|
validate,
|
|
70493
70828
|
root: false
|
|
70494
70829
|
});
|
|
@@ -70498,7 +70833,7 @@ var AiassistantRule = class AiassistantRule extends ToolRule {
|
|
|
70498
70833
|
outputRoot,
|
|
70499
70834
|
relativeDirPath,
|
|
70500
70835
|
relativeFilePath,
|
|
70501
|
-
|
|
70836
|
+
body: "",
|
|
70502
70837
|
validate: false,
|
|
70503
70838
|
root: false
|
|
70504
70839
|
});
|
|
@@ -72055,40 +72390,14 @@ const CodebuddyRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
72055
72390
|
enabled: zod_mini.z.optional(zod_mini.z.boolean())
|
|
72056
72391
|
});
|
|
72057
72392
|
/**
|
|
72058
|
-
* Splits a scalar `paths` value on the commas that separate patterns, leaving
|
|
72059
|
-
* the commas inside a brace group alone: CodeBuddy's memory docs say "You can
|
|
72060
|
-
* also combine multiple patterns with commas" and give
|
|
72061
|
-
* `paths: {src,lib}/**\/*.ts, tests/**\/*.test.ts` as the example, where the
|
|
72062
|
-
* first comma belongs to the brace expansion and the second separates the two
|
|
72063
|
-
* globs. Only a scalar is split — the docs describe the comma form for the
|
|
72064
|
-
* string shape, and a list already separates its patterns.
|
|
72065
|
-
*/
|
|
72066
|
-
function splitCodebuddyPathsScalar(paths) {
|
|
72067
|
-
const patterns = [];
|
|
72068
|
-
let current = "";
|
|
72069
|
-
let braceDepth = 0;
|
|
72070
|
-
for (const char of paths) {
|
|
72071
|
-
if (char === "{") braceDepth += 1;
|
|
72072
|
-
else if (char === "}" && braceDepth > 0) braceDepth -= 1;
|
|
72073
|
-
else if (char === "," && braceDepth === 0) {
|
|
72074
|
-
patterns.push(current);
|
|
72075
|
-
current = "";
|
|
72076
|
-
continue;
|
|
72077
|
-
}
|
|
72078
|
-
current += char;
|
|
72079
|
-
}
|
|
72080
|
-
patterns.push(current);
|
|
72081
|
-
return patterns.map((pattern) => pattern.trim());
|
|
72082
|
-
}
|
|
72083
|
-
/**
|
|
72084
72393
|
* Normalizes the documented `string` / `string[]` shapes of `paths` to the
|
|
72085
72394
|
* list form the rest of the adapter works with, splitting a comma-separated
|
|
72086
|
-
* scalar into its patterns (see `
|
|
72395
|
+
* scalar into its patterns (see `splitBraceAwareList`). An empty list
|
|
72087
72396
|
* and an empty string both mean "no paths".
|
|
72088
72397
|
*/
|
|
72089
72398
|
function normalizeCodebuddyPaths(paths) {
|
|
72090
72399
|
if (paths === void 0) return;
|
|
72091
|
-
const list = (typeof paths === "string" ?
|
|
72400
|
+
const list = (typeof paths === "string" ? splitBraceAwareList(paths) : paths).filter((path) => path.trim() !== "");
|
|
72092
72401
|
return list.length > 0 ? list : void 0;
|
|
72093
72402
|
}
|
|
72094
72403
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_import = require("./import-
|
|
2
|
+
const require_import = require("./import-xo5me5dm.cjs");
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
async function generate(options = {}) {
|
|
5
5
|
const { silent = true, verbose = false, ...rest } = options;
|
package/dist/index.d.cts
CHANGED
|
@@ -499,6 +499,8 @@ declare const RulesyncSkillFrontmatterSchemaInternal: z.ZodMiniObject<{
|
|
|
499
499
|
paths: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniString<string>, z.ZodMiniArray<z.ZodMiniString<string>>]>>;
|
|
500
500
|
"disable-model-invocation": z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
501
501
|
"user-invocable": z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
502
|
+
icon: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
503
|
+
color: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
502
504
|
metadata: z.ZodMiniOptional<z.ZodMiniObject<{}, z.core.$loose>>;
|
|
503
505
|
}, z.core.$loose>>;
|
|
504
506
|
factorydroid: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
@@ -725,6 +727,8 @@ type RulesyncSkillFrontmatterInput = {
|
|
|
725
727
|
paths?: string | string[];
|
|
726
728
|
"disable-model-invocation"?: boolean;
|
|
727
729
|
"user-invocable"?: boolean;
|
|
730
|
+
icon?: string;
|
|
731
|
+
color?: string;
|
|
728
732
|
metadata?: Record<string, unknown>;
|
|
729
733
|
};
|
|
730
734
|
factorydroid?: {
|