rulesync 33.0.0 → 34.0.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 +3 -2
- package/dist/cli/index.cjs +409 -125
- package/dist/cli/index.js +409 -125
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-D_SV_C0k.cjs → import-Bx0zNGBx.cjs} +590 -127
- package/dist/{import-5-Uennl1.js → import-DFwX4TTE.js} +563 -112
- package/dist/{import-5-Uennl1.js.map → import-DFwX4TTE.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +15 -15
|
@@ -386,7 +386,11 @@ const DeprecatedIgnoreFeatureSchema = z.literal("ignore").check(meta({
|
|
|
386
386
|
const FeatureSchema = z.union([z.enum(ACTIVE_FEATURES), DeprecatedIgnoreFeatureSchema]);
|
|
387
387
|
z.array(FeatureSchema);
|
|
388
388
|
const FeatureWithWildcardSchema = z.union([z.enum([...ACTIVE_FEATURES, "*"]), DeprecatedIgnoreFeatureSchema]);
|
|
389
|
-
const GitignoreDestinationSchema = z.enum([
|
|
389
|
+
const GitignoreDestinationSchema = z.enum([
|
|
390
|
+
"gitignore",
|
|
391
|
+
"gitattributes",
|
|
392
|
+
"gitexclude"
|
|
393
|
+
]);
|
|
390
394
|
const FlattenedCommandNamingSchema = z.enum(["basename", "path"]);
|
|
391
395
|
const FeatureOptionsSchema = z.record(z.string(), z.unknown());
|
|
392
396
|
const FeatureValueSchema = z.union([
|
|
@@ -506,6 +510,7 @@ const rulesProcessorToolTargetTuple = [
|
|
|
506
510
|
"claudecode-legacy",
|
|
507
511
|
"cline",
|
|
508
512
|
"codebuddy",
|
|
513
|
+
"codebuff",
|
|
509
514
|
"codewhale",
|
|
510
515
|
"codexcli",
|
|
511
516
|
"commandcode",
|
|
@@ -1385,6 +1390,13 @@ async function getFileSize(filepath) {
|
|
|
1385
1390
|
throw new Error(`Failed to get file size for "${filepath}": ${formatError(error)}`, { cause: error });
|
|
1386
1391
|
}
|
|
1387
1392
|
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Whether a file carries any execute bit. Windows reports none, so a file
|
|
1395
|
+
* there is never seen as executable.
|
|
1396
|
+
*/
|
|
1397
|
+
async function hasExecutableBit(filepath) {
|
|
1398
|
+
return ((await stat(filepath)).mode & 73) !== 0;
|
|
1399
|
+
}
|
|
1388
1400
|
async function isSymlink(filepath) {
|
|
1389
1401
|
try {
|
|
1390
1402
|
return (await lstat(filepath)).isSymbolicLink();
|
|
@@ -3157,6 +3169,9 @@ var Config = class Config {
|
|
|
3157
3169
|
const featureValue = value[feature];
|
|
3158
3170
|
if (featureValue && typeof featureValue === "object" && isFeatureValueEnabled(featureValue)) return featureValue;
|
|
3159
3171
|
}
|
|
3172
|
+
getRootGitignoreDestination() {
|
|
3173
|
+
return this.gitignoreDestination;
|
|
3174
|
+
}
|
|
3160
3175
|
getGitignoreDestination(target, feature) {
|
|
3161
3176
|
const rootLevel = this.gitignoreDestination;
|
|
3162
3177
|
if (!isRulesyncConfigTargetsObject(this.targets)) return rootLevel;
|
|
@@ -3174,7 +3189,8 @@ var Config = class Config {
|
|
|
3174
3189
|
return toolLevel ?? rootLevel;
|
|
3175
3190
|
}
|
|
3176
3191
|
static parseGitignoreDestination(value) {
|
|
3177
|
-
|
|
3192
|
+
const result = GitignoreDestinationSchema.safeParse(value);
|
|
3193
|
+
return result.success ? result.data : void 0;
|
|
3178
3194
|
}
|
|
3179
3195
|
/**
|
|
3180
3196
|
* Check if per-target features configuration is being used.
|
|
@@ -5244,12 +5260,20 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
5244
5260
|
"sessionEnd"
|
|
5245
5261
|
];
|
|
5246
5262
|
/**
|
|
5247
|
-
* Hook events supported by the GitLab Duo CLI.
|
|
5248
|
-
*
|
|
5249
|
-
*
|
|
5263
|
+
* Hook events supported by the GitLab Duo CLI. `SessionStart` is documented;
|
|
5264
|
+
* `PreToolUse` and `SessionEnd` ship in gitlab-lsp (`HOOK_EVENT_NAMES`, v9.28.0+)
|
|
5265
|
+
* but are not on docs.gitlab.com yet. The optional regex matcher filters on the
|
|
5266
|
+
* session source (`startup`, `resume`) for `SessionStart`, on `tool_name` for
|
|
5267
|
+
* `PreToolUse`, and on the end reason for `SessionEnd`. Only `type: "command"`
|
|
5268
|
+
* hooks are supported.
|
|
5250
5269
|
* @see https://docs.gitlab.com/user/gitlab_duo_cli/customize/
|
|
5270
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/main/packages/core/hooks/src/schema.ts
|
|
5251
5271
|
*/
|
|
5252
|
-
const GITLABDUO_HOOK_EVENTS = [
|
|
5272
|
+
const GITLABDUO_HOOK_EVENTS = [
|
|
5273
|
+
"sessionStart",
|
|
5274
|
+
"preToolUse",
|
|
5275
|
+
"sessionEnd"
|
|
5276
|
+
];
|
|
5253
5277
|
/**
|
|
5254
5278
|
* Hook events supported by Qwen Code.
|
|
5255
5279
|
*
|
|
@@ -5590,7 +5614,10 @@ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
5590
5614
|
* or `agent` hooks with a `timeout` in seconds. The hooks reference documents ten events; the
|
|
5591
5615
|
* plugins reference lists the full set, stating that plugin hooks "respond to
|
|
5592
5616
|
* the same lifecycle events as user-defined hooks", so every event listed
|
|
5593
|
-
* there is supported here.
|
|
5617
|
+
* there is supported here. The hooks reference also names `Setup`
|
|
5618
|
+
* (bootstrap/maintenance), and the published package's settings schema
|
|
5619
|
+
* (`@tencent-ai/codebuddy-code` 2.167.0) accepts `UserPromptExpansion`, whose
|
|
5620
|
+
* matcher filters on `command_name` as in Claude Code.
|
|
5594
5621
|
*
|
|
5595
5622
|
* @see https://www.codebuddy.ai/docs/cli/hooks
|
|
5596
5623
|
* @see https://www.codebuddy.ai/docs/cli/plugins-reference
|
|
@@ -5621,7 +5648,9 @@ const CODEBUDDY_HOOK_EVENTS = [
|
|
|
5621
5648
|
"postCompact",
|
|
5622
5649
|
"elicitation",
|
|
5623
5650
|
"elicitationResult",
|
|
5624
|
-
"sessionEnd"
|
|
5651
|
+
"sessionEnd",
|
|
5652
|
+
"setup",
|
|
5653
|
+
"userPromptExpansion"
|
|
5625
5654
|
];
|
|
5626
5655
|
/**
|
|
5627
5656
|
* CodeBuddy Code events that fire on every occurrence and take no `matcher`.
|
|
@@ -5670,7 +5699,9 @@ const CANONICAL_TO_CODEBUDDY_EVENT_NAMES = {
|
|
|
5670
5699
|
postCompact: "PostCompact",
|
|
5671
5700
|
elicitation: "Elicitation",
|
|
5672
5701
|
elicitationResult: "ElicitationResult",
|
|
5673
|
-
sessionEnd: "SessionEnd"
|
|
5702
|
+
sessionEnd: "SessionEnd",
|
|
5703
|
+
setup: "Setup",
|
|
5704
|
+
userPromptExpansion: "UserPromptExpansion"
|
|
5674
5705
|
};
|
|
5675
5706
|
const CODEBUDDY_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CODEBUDDY_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
5676
5707
|
/**
|
|
@@ -6524,7 +6555,11 @@ const KIRO_IDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
6524
6555
|
/**
|
|
6525
6556
|
* Map canonical camelCase event names to GitLab Duo CLI PascalCase.
|
|
6526
6557
|
*/
|
|
6527
|
-
const CANONICAL_TO_GITLABDUO_EVENT_NAMES = {
|
|
6558
|
+
const CANONICAL_TO_GITLABDUO_EVENT_NAMES = {
|
|
6559
|
+
sessionStart: "SessionStart",
|
|
6560
|
+
preToolUse: "PreToolUse",
|
|
6561
|
+
sessionEnd: "SessionEnd"
|
|
6562
|
+
};
|
|
6528
6563
|
/**
|
|
6529
6564
|
* Map GitLab Duo CLI PascalCase event names to canonical camelCase.
|
|
6530
6565
|
*/
|
|
@@ -8265,6 +8300,48 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
8265
8300
|
]))
|
|
8266
8301
|
});
|
|
8267
8302
|
/**
|
|
8303
|
+
* Tool-scoped override block for Qoder. `outputStyle` is written to the
|
|
8304
|
+
* top-level `outputStyle` key of `.qoder/settings.json` (project) /
|
|
8305
|
+
* `~/.qoder/settings.json` (global): the name of the active output style, a
|
|
8306
|
+
* custom style from `.qoder/output-styles/` (see the `qoder.channel:
|
|
8307
|
+
* output-style` rule option) or a `plugin-name:style-name` reference. The
|
|
8308
|
+
* shared `permission` block continues to drive `permissions.allow`/`ask`/`deny`.
|
|
8309
|
+
*
|
|
8310
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
8311
|
+
* @see https://docs.qoder.com/cli/settings-reference
|
|
8312
|
+
* @example
|
|
8313
|
+
* { "outputStyle": "concise-review" }
|
|
8314
|
+
*/
|
|
8315
|
+
const QoderPermissionsOverrideSchema = z.looseObject({
|
|
8316
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
8317
|
+
outputStyle: z.optional(z.string().check(z.trim(), z.minLength(1)))
|
|
8318
|
+
});
|
|
8319
|
+
/**
|
|
8320
|
+
* One native Factory Droid permission rule. `match.prefix` positions are either
|
|
8321
|
+
* a single token or a non-empty list of alternative tokens.
|
|
8322
|
+
*
|
|
8323
|
+
* @see https://docs.factory.com/autonomy-and-safety/permission-rules
|
|
8324
|
+
*/
|
|
8325
|
+
const FactorydroidPermissionRuleSchema = z.looseObject({
|
|
8326
|
+
id: z.string(),
|
|
8327
|
+
decision: z.enum([
|
|
8328
|
+
"allow",
|
|
8329
|
+
"ask",
|
|
8330
|
+
"block"
|
|
8331
|
+
]),
|
|
8332
|
+
match: z.looseObject({ prefix: z.optional(z.array(z.union([z.string(), z.array(z.string()).check(z.minLength(1))])).check(z.minLength(1))) }),
|
|
8333
|
+
tests: z.optional(z.looseObject({
|
|
8334
|
+
match: z.optional(z.array(z.string())),
|
|
8335
|
+
noMatch: z.optional(z.array(z.string()))
|
|
8336
|
+
})),
|
|
8337
|
+
reason: z.optional(z.string()),
|
|
8338
|
+
enabled: z.optional(z.boolean())
|
|
8339
|
+
});
|
|
8340
|
+
const FactorydroidPermissionRulesSchema = z.looseObject({
|
|
8341
|
+
version: z.number(),
|
|
8342
|
+
rules: z.array(FactorydroidPermissionRuleSchema)
|
|
8343
|
+
});
|
|
8344
|
+
/**
|
|
8268
8345
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
8269
8346
|
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
8270
8347
|
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
@@ -8272,7 +8349,8 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
8272
8349
|
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
8273
8350
|
* `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
|
|
8274
8351
|
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
8275
|
-
* `subagentAutonomyLevel`, `interactionMode`)
|
|
8352
|
+
* `subagentAutonomyLevel`, `interactionMode`), and the native versioned
|
|
8353
|
+
* `permissionRules` (allow/ask/block). Fields placed here are merged
|
|
8276
8354
|
* into `settings.json` and
|
|
8277
8355
|
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
8278
8356
|
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
@@ -8283,7 +8361,8 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
8283
8361
|
const FactorydroidPermissionsOverrideSchema = z.looseObject({
|
|
8284
8362
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
8285
8363
|
commandBlocklist: z.optional(z.array(z.string())),
|
|
8286
|
-
disabledSkills: z.optional(z.array(z.string()))
|
|
8364
|
+
disabledSkills: z.optional(z.array(z.string())),
|
|
8365
|
+
permissionRules: z.optional(FactorydroidPermissionRulesSchema)
|
|
8287
8366
|
});
|
|
8288
8367
|
/**
|
|
8289
8368
|
* Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
|
|
@@ -9088,6 +9167,7 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
9088
9167
|
codewhale: z.optional(CodewhalePermissionsOverrideSchema),
|
|
9089
9168
|
zed: z.optional(ZedPermissionsOverrideSchema),
|
|
9090
9169
|
devin: z.optional(DevinPermissionsOverrideSchema),
|
|
9170
|
+
qoder: z.optional(QoderPermissionsOverrideSchema),
|
|
9091
9171
|
"antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
|
|
9092
9172
|
continue: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9093
9173
|
copilot: z.optional(CopilotPermissionsOverrideSchema),
|
|
@@ -9099,7 +9179,6 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
9099
9179
|
goose: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9100
9180
|
omp: z.optional(OmpPermissionsOverrideSchema),
|
|
9101
9181
|
pool: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9102
|
-
qoder: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9103
9182
|
grokcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9104
9183
|
"kimi-code": z.optional(KimiCodePermissionsOverrideSchema),
|
|
9105
9184
|
roo: z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -9620,7 +9699,9 @@ const RulesyncRuleFrontmatterSchema = z.object({
|
|
|
9620
9699
|
qoder: z.optional(z.looseObject({
|
|
9621
9700
|
trigger: z.optional(z.string()),
|
|
9622
9701
|
glob: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
9623
|
-
description: z.optional(z.string())
|
|
9702
|
+
description: z.optional(z.string()),
|
|
9703
|
+
channel: z.optional(z.enum(["output-style"])),
|
|
9704
|
+
name: z.optional(z.string().check(z.trim(), z.minLength(1)))
|
|
9624
9705
|
})),
|
|
9625
9706
|
augmentcode: z.optional(z.looseObject({
|
|
9626
9707
|
type: z.optional(z.string()),
|
|
@@ -12989,7 +13070,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12989
13070
|
},
|
|
12990
13071
|
permissions: {
|
|
12991
13072
|
kind: "replace-owned-keys",
|
|
12992
|
-
ownedKeys: ["permissions"]
|
|
13073
|
+
ownedKeys: ["permissions", "outputStyle"]
|
|
12993
13074
|
}
|
|
12994
13075
|
}
|
|
12995
13076
|
},
|
|
@@ -22403,6 +22484,7 @@ const QODER_DIR = ".qoder";
|
|
|
22403
22484
|
const QODER_RULE_FILE_NAME = "AGENTS.md";
|
|
22404
22485
|
const QODER_LOCAL_RULE_FILE_NAME = "AGENTS.local.md";
|
|
22405
22486
|
const QODER_RULES_DIR_PATH = join(QODER_DIR, "rules");
|
|
22487
|
+
const QODER_OUTPUT_STYLES_DIR_PATH = join(QODER_DIR, "output-styles");
|
|
22406
22488
|
const QODER_SETTINGS_FILE_NAME = "settings.json";
|
|
22407
22489
|
const QODER_PROJECT_MCP_FILE_NAME = ".mcp.json";
|
|
22408
22490
|
const QODER_COMMANDS_DIR_PATH = join(QODER_DIR, "commands");
|
|
@@ -29777,6 +29859,11 @@ const CORTEXCODE_SPEC = {
|
|
|
29777
29859
|
canonical: "enabled",
|
|
29778
29860
|
tool: "enabled"
|
|
29779
29861
|
}],
|
|
29862
|
+
stringPassthroughFields: [{
|
|
29863
|
+
canonical: "statusMessage",
|
|
29864
|
+
tool: "statusMessage",
|
|
29865
|
+
commandOnly: true
|
|
29866
|
+
}],
|
|
29780
29867
|
objectPassthroughFields: [{
|
|
29781
29868
|
canonical: "source",
|
|
29782
29869
|
tool: "source",
|
|
@@ -29791,7 +29878,7 @@ const CORTEXCODE_SPEC = {
|
|
|
29791
29878
|
* (project scope) and of the dedicated `~/.snowflake/cortex/hooks.json` (user
|
|
29792
29879
|
* scope), both in the Claude-Code shape: `{ "<Event>": [{ "matcher"?:
|
|
29793
29880
|
* "<regex>", "hooks": [{ "type": "command" | "prompt", ..., "timeout"?:
|
|
29794
|
-
* <seconds>, "enabled"?: <boolean>, "source"?: { "source", "ref"? } }] }] }`.
|
|
29881
|
+
* <seconds>, "enabled"?: <boolean>, "statusMessage"?: <string> (command only), "source"?: { "source", "ref"? } }] }] }`.
|
|
29795
29882
|
* The project file also holds settings rulesync does not own, so generation
|
|
29796
29883
|
* merges the `hooks` key into
|
|
29797
29884
|
* either file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it, and
|
|
@@ -30730,7 +30817,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
30730
30817
|
"allowManagedHooksOnly",
|
|
30731
30818
|
"disabledSkills",
|
|
30732
30819
|
"modelPolicy",
|
|
30733
|
-
"missionPolicy"
|
|
30820
|
+
"missionPolicy",
|
|
30821
|
+
"permissionRules"
|
|
30734
30822
|
];
|
|
30735
30823
|
//#endregion
|
|
30736
30824
|
//#region src/utils/factorydroid-settings.ts
|
|
@@ -30945,7 +31033,7 @@ const GITLABDUO_CONVERTER_CONFIG = {
|
|
|
30945
31033
|
*
|
|
30946
31034
|
* Hooks live in a dedicated file, `<project>/.gitlab/duo/hooks.json` (project,
|
|
30947
31035
|
* opt-in via `--enable-project-hooks` / `GITLAB_ENABLE_PROJECT_HOOKS=true`) or
|
|
30948
|
-
* `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...] } }`.
|
|
31036
|
+
* `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...], "PreToolUse": [...] } }`.
|
|
30949
31037
|
* rulesync owns the whole file, so it is regenerated and deleted as a unit.
|
|
30950
31038
|
*/
|
|
30951
31039
|
var GitlabduoHooks = class GitlabduoHooks extends ToolHooks {
|
|
@@ -36599,6 +36687,7 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
|
|
|
36599
36687
|
const CODEBUFF_IGNORE_FILE_NAME = ".codebuffignore";
|
|
36600
36688
|
const CODEBUFF_AGENTS_DIR = ".agents";
|
|
36601
36689
|
const CODEBUFF_MCP_FILE_NAME = "mcp.json";
|
|
36690
|
+
const CODEBUFF_GLOBAL_RULE_FILE_NAME = ".AGENTS.md";
|
|
36602
36691
|
//#endregion
|
|
36603
36692
|
//#region src/features/ignore/codebuff-ignore.ts
|
|
36604
36693
|
/**
|
|
@@ -43500,11 +43589,45 @@ const CANONICAL_TO_GITLABDUO_TRANSPORT = {
|
|
|
43500
43589
|
sse: "sse"
|
|
43501
43590
|
};
|
|
43502
43591
|
/**
|
|
43592
|
+
* Map the canonical `timeout` (milliseconds) onto GitLab Duo's `timeoutMs`, a
|
|
43593
|
+
* per-server tool-call inactivity timeout (gitlab-lsp v9.25.0+) that must be a
|
|
43594
|
+
* positive integer. An explicit `timeoutMs` on the same server wins; a
|
|
43595
|
+
* `timeout` Duo would reject is dropped with a warning.
|
|
43596
|
+
*
|
|
43597
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/e8c4a76a66e8f37d4c97fc03ffe983bfadf43f76
|
|
43598
|
+
*/
|
|
43599
|
+
function toGitlabduoTimeout({ serverName, server, logger }) {
|
|
43600
|
+
const { timeout, ...rest } = server;
|
|
43601
|
+
if (timeout === void 0 || rest.timeoutMs !== void 0) return rest;
|
|
43602
|
+
if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout <= 0) {
|
|
43603
|
+
logger?.warn(`GitLab Duo CLI MCP server "${serverName}": dropping timeout ${JSON.stringify(timeout)}, as timeoutMs must be a positive integer of milliseconds.`);
|
|
43604
|
+
return rest;
|
|
43605
|
+
}
|
|
43606
|
+
return {
|
|
43607
|
+
...rest,
|
|
43608
|
+
timeoutMs: timeout
|
|
43609
|
+
};
|
|
43610
|
+
}
|
|
43611
|
+
/**
|
|
43612
|
+
* Read GitLab Duo's `timeoutMs` back into the canonical `timeout`
|
|
43613
|
+
* (milliseconds). A server that already carries a `timeout` is left as is, so
|
|
43614
|
+
* both keys round-trip and `timeoutMs` keeps winning on the next generate.
|
|
43615
|
+
*/
|
|
43616
|
+
function fromGitlabduoTimeout(server) {
|
|
43617
|
+
if (typeof server !== "object" || server === null || Array.isArray(server)) return server;
|
|
43618
|
+
const { timeoutMs, ...rest } = server;
|
|
43619
|
+
if (timeoutMs === void 0 || rest.timeout !== void 0) return server;
|
|
43620
|
+
return {
|
|
43621
|
+
...rest,
|
|
43622
|
+
timeout: timeoutMs
|
|
43623
|
+
};
|
|
43624
|
+
}
|
|
43625
|
+
/**
|
|
43503
43626
|
* Convert one canonical server into GitLab Duo's shape. Every documented
|
|
43504
43627
|
* example spells the transport out as `type` (`stdio`, `http` or `sse`), so it
|
|
43505
43628
|
* is always written, inferred from `command` / `url` when the source leaves it
|
|
43506
|
-
* implicit.
|
|
43507
|
-
* through as authored.
|
|
43629
|
+
* implicit. The canonical `timeout` becomes `timeoutMs`; every other field —
|
|
43630
|
+
* `args`, `env`, `approvedTools`, ... — is carried through as authored.
|
|
43508
43631
|
*/
|
|
43509
43632
|
function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
43510
43633
|
if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
|
|
@@ -43513,7 +43636,7 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
43513
43636
|
reason: "no command or url",
|
|
43514
43637
|
logger
|
|
43515
43638
|
});
|
|
43516
|
-
const { type, transport, httpUrl: _httpUrl, url: _url, ...
|
|
43639
|
+
const { type, transport, httpUrl: _httpUrl, url: _url, ...others } = serverConfig;
|
|
43517
43640
|
const declared = type ?? transport;
|
|
43518
43641
|
const mapped = declared ? CANONICAL_TO_GITLABDUO_TRANSPORT[declared] : isRemoteMcpServer(serverConfig) ? "http" : "stdio";
|
|
43519
43642
|
if (!mapped) return warnAndSkipMcpServer({
|
|
@@ -43522,8 +43645,13 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
43522
43645
|
reason: `the unsupported transport "${declared}" (GitLab Duo CLI accepts stdio, http and sse)`,
|
|
43523
43646
|
logger
|
|
43524
43647
|
});
|
|
43648
|
+
const rest = toGitlabduoTimeout({
|
|
43649
|
+
serverName,
|
|
43650
|
+
server: others,
|
|
43651
|
+
logger
|
|
43652
|
+
});
|
|
43525
43653
|
if (mapped === "stdio") {
|
|
43526
|
-
const { command, args } =
|
|
43654
|
+
const { command, args } = serverConfig;
|
|
43527
43655
|
if (Array.isArray(command)) {
|
|
43528
43656
|
const [executable, ...leadingArgs] = command;
|
|
43529
43657
|
return {
|
|
@@ -43612,7 +43740,13 @@ var GitlabduoMcp = class GitlabduoMcp extends ToolMcp {
|
|
|
43612
43740
|
});
|
|
43613
43741
|
}
|
|
43614
43742
|
toRulesyncMcp() {
|
|
43615
|
-
|
|
43743
|
+
const { mcpServers, ...others } = this.json;
|
|
43744
|
+
if (typeof mcpServers !== "object" || mcpServers === null || Array.isArray(mcpServers)) return this.toRulesyncMcpDefault();
|
|
43745
|
+
const servers = Object.fromEntries(Object.entries(mcpServers).map(([name, server]) => [name, fromGitlabduoTimeout(server)]));
|
|
43746
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
43747
|
+
...others,
|
|
43748
|
+
mcpServers: servers
|
|
43749
|
+
}) });
|
|
43616
43750
|
}
|
|
43617
43751
|
validate() {
|
|
43618
43752
|
return {
|
|
@@ -46719,6 +46853,15 @@ const CONSUMED_CANONICAL_KEYS = /* @__PURE__ */ new Set([
|
|
|
46719
46853
|
]);
|
|
46720
46854
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
46721
46855
|
/**
|
|
46856
|
+
* The only keys a project override entry for a user-level server carries:
|
|
46857
|
+
* Pi reads a `.pi/mcp.json` entry without `command`, `url` or `type` as an
|
|
46858
|
+
* override of `enabled`, `exposure` and `toolExposure` of the user-level server
|
|
46859
|
+
* with the same name, and keeps the rest of that server.
|
|
46860
|
+
*
|
|
46861
|
+
* @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/docs/mcp.md
|
|
46862
|
+
*/
|
|
46863
|
+
const PI_OVERRIDE_PASSTHROUGH_KEYS = ["exposure", "toolExposure"];
|
|
46864
|
+
/**
|
|
46722
46865
|
* Pi rejects a server whose name has any other character, and treats names
|
|
46723
46866
|
* that differ only in `-` and `_` as one server (its tools share the
|
|
46724
46867
|
* `mcp__<name>` namespace with `-` replaced by `_`), rejecting the second.
|
|
@@ -46754,17 +46897,12 @@ function hasUnexpandedEnvVarRef(serverConfig) {
|
|
|
46754
46897
|
/**
|
|
46755
46898
|
* The transport fields Pi reads for one server: `type` `stdio` with a string
|
|
46756
46899
|
* `command` plus `args`, or `type` `http` with a `url`. A server Pi cannot
|
|
46757
|
-
* start or reach —
|
|
46758
|
-
*
|
|
46759
|
-
*
|
|
46900
|
+
* start or reach — a remote transport without a URL, an SSE or WebSocket
|
|
46901
|
+
* server, or a stdio entry without a command — is rejected by Pi, so it is
|
|
46902
|
+
* skipped with a warning (`null`). A server with no transport at all is a
|
|
46903
|
+
* project override instead (see `convertOverrideToPi`).
|
|
46760
46904
|
*/
|
|
46761
46905
|
function convertTransportToPi({ serverName, serverConfig, logger }) {
|
|
46762
|
-
if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
|
|
46763
|
-
toolName: "Pi",
|
|
46764
|
-
serverName,
|
|
46765
|
-
reason: "no transport",
|
|
46766
|
-
logger
|
|
46767
|
-
});
|
|
46768
46906
|
if (isRemoteMcpServer(serverConfig)) {
|
|
46769
46907
|
const url = resolveRemoteMcpUrl(serverConfig);
|
|
46770
46908
|
const stated = serverConfig.type ?? serverConfig.transport;
|
|
@@ -46805,56 +46943,117 @@ function convertTransportToPi({ serverName, serverConfig, logger }) {
|
|
|
46805
46943
|
};
|
|
46806
46944
|
}
|
|
46807
46945
|
/**
|
|
46808
|
-
*
|
|
46809
|
-
*
|
|
46810
|
-
*
|
|
46811
|
-
*
|
|
46812
|
-
*
|
|
46946
|
+
* A transport-less server in the project file becomes a Pi override entry for
|
|
46947
|
+
* the user-level server of the same name. `disabled` maps to `enabled` both
|
|
46948
|
+
* ways (an explicit `disabled: false` is "Enable in this project"), and any key
|
|
46949
|
+
* Pi ignores on an override is dropped with a warning. The global file has no
|
|
46950
|
+
* layer below it to override, so the entry is skipped there.
|
|
46951
|
+
*/
|
|
46952
|
+
function convertOverrideToPi({ serverName, serverConfig, global, logger }) {
|
|
46953
|
+
if (global) return warnAndSkipMcpServer({
|
|
46954
|
+
toolName: "Pi",
|
|
46955
|
+
serverName,
|
|
46956
|
+
reason: "no transport",
|
|
46957
|
+
logger
|
|
46958
|
+
});
|
|
46959
|
+
const record = serverConfig;
|
|
46960
|
+
const override = {};
|
|
46961
|
+
if (typeof serverConfig.disabled === "boolean") override.enabled = !serverConfig.disabled;
|
|
46962
|
+
for (const key of PI_OVERRIDE_PASSTHROUGH_KEYS) if (record[key] !== void 0) override[key] = record[key];
|
|
46963
|
+
if (Object.keys(override).length === 0) return warnAndSkipMcpServer({
|
|
46964
|
+
toolName: "Pi",
|
|
46965
|
+
serverName,
|
|
46966
|
+
reason: "no transport and nothing a Pi project override sets (disabled, exposure, toolExposure)",
|
|
46967
|
+
logger
|
|
46968
|
+
});
|
|
46969
|
+
const droppedKeys = Object.keys(record).filter((key) => key !== "disabled" && !PI_OVERRIDE_PASSTHROUGH_KEYS.includes(key) && !PROTOTYPE_POLLUTION_KEYS.has(key));
|
|
46970
|
+
if (droppedKeys.length > 0) logger?.warn(`Pi MCP: dropping ${droppedKeys.map((key) => quoteValueForWarning(key)).join(", ")} of the project override ${quoteValueForWarning(serverName)}: Pi reads only enabled, exposure and toolExposure from an entry without a transport.`);
|
|
46971
|
+
return override;
|
|
46972
|
+
}
|
|
46973
|
+
/**
|
|
46974
|
+
* Whether Pi rejects the server name, either outright or because an earlier
|
|
46975
|
+
* server already took its `mcp__<name>` namespace; warns when it does.
|
|
46976
|
+
*/
|
|
46977
|
+
function isRejectedServerName({ serverName, namespaces, logger }) {
|
|
46978
|
+
if (!PI_SERVER_NAME_PATTERN.test(serverName)) {
|
|
46979
|
+
warnAndSkipMcpServer({
|
|
46980
|
+
toolName: "Pi",
|
|
46981
|
+
serverName,
|
|
46982
|
+
reason: "a name Pi rejects (use letters, digits, \"_\" and \"-\")",
|
|
46983
|
+
logger
|
|
46984
|
+
});
|
|
46985
|
+
return true;
|
|
46986
|
+
}
|
|
46987
|
+
const clash = namespaces.get(toPiNamespace(serverName));
|
|
46988
|
+
if (clash !== void 0) {
|
|
46989
|
+
warnAndSkipMcpServer({
|
|
46990
|
+
toolName: "Pi",
|
|
46991
|
+
serverName,
|
|
46992
|
+
reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
|
|
46993
|
+
logger
|
|
46994
|
+
});
|
|
46995
|
+
return true;
|
|
46996
|
+
}
|
|
46997
|
+
return false;
|
|
46998
|
+
}
|
|
46999
|
+
/**
|
|
47000
|
+
* Convert one canonical server to its Pi entry, or `null` when it is skipped.
|
|
47001
|
+
* Besides the transport fields, Pi switches a server off with `enabled: false`
|
|
47002
|
+
* and reads `timeout` in seconds (canonical: milliseconds). `auth` is dropped
|
|
47003
|
+
* with a warning from the project file, since Pi accepts it only in the global
|
|
47004
|
+
* one (it drops the whole server otherwise). A server without a transport is a
|
|
47005
|
+
* project override entry instead.
|
|
47006
|
+
*/
|
|
47007
|
+
function convertServerToPi({ serverName, serverConfig, global, logger }) {
|
|
47008
|
+
if (declaresNoTransport(serverConfig)) return convertOverrideToPi({
|
|
47009
|
+
serverName,
|
|
47010
|
+
serverConfig,
|
|
47011
|
+
global,
|
|
47012
|
+
logger
|
|
47013
|
+
});
|
|
47014
|
+
const converted = convertTransportToPi({
|
|
47015
|
+
serverName,
|
|
47016
|
+
serverConfig,
|
|
47017
|
+
logger
|
|
47018
|
+
});
|
|
47019
|
+
if (!converted) return null;
|
|
47020
|
+
for (const [key, value] of Object.entries(serverConfig)) {
|
|
47021
|
+
if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
47022
|
+
converted[key] = value;
|
|
47023
|
+
}
|
|
47024
|
+
if (serverConfig.timeout !== void 0) {
|
|
47025
|
+
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
|
|
47026
|
+
else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
|
|
47027
|
+
}
|
|
47028
|
+
if (serverConfig.disabled === true) converted.enabled = false;
|
|
47029
|
+
if (!global && "auth" in converted) {
|
|
47030
|
+
delete converted.auth;
|
|
47031
|
+
logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
|
|
47032
|
+
}
|
|
47033
|
+
if (hasUnexpandedEnvVarRef(converted)) logger?.warn(`Pi MCP: ${quoteValueForWarning(serverName)} carries an environment variable reference Pi does not expand. Pi expands only \${VAR} and $VAR in env and headers values, and only a leading ~/ in command, args and cwd.`);
|
|
47034
|
+
return converted;
|
|
47035
|
+
}
|
|
47036
|
+
/**
|
|
47037
|
+
* Convert the canonical server map to Pi's `mcpServers` shape. A server name
|
|
47038
|
+
* Pi rejects is skipped with a warning.
|
|
46813
47039
|
*/
|
|
46814
47040
|
function convertToPiFormat({ mcpServers, global, logger }) {
|
|
46815
47041
|
const result = {};
|
|
46816
47042
|
const namespaces = /* @__PURE__ */ new Map();
|
|
46817
47043
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
46818
47044
|
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
46819
|
-
if (
|
|
46820
|
-
|
|
46821
|
-
|
|
46822
|
-
|
|
46823
|
-
|
|
46824
|
-
|
|
46825
|
-
});
|
|
46826
|
-
continue;
|
|
46827
|
-
}
|
|
46828
|
-
const clash = namespaces.get(toPiNamespace(serverName));
|
|
46829
|
-
if (clash !== void 0) {
|
|
46830
|
-
warnAndSkipMcpServer({
|
|
46831
|
-
toolName: "Pi",
|
|
46832
|
-
serverName,
|
|
46833
|
-
reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
|
|
46834
|
-
logger
|
|
46835
|
-
});
|
|
46836
|
-
continue;
|
|
46837
|
-
}
|
|
46838
|
-
const converted = convertTransportToPi({
|
|
47045
|
+
if (isRejectedServerName({
|
|
47046
|
+
serverName,
|
|
47047
|
+
namespaces,
|
|
47048
|
+
logger
|
|
47049
|
+
})) continue;
|
|
47050
|
+
const converted = convertServerToPi({
|
|
46839
47051
|
serverName,
|
|
46840
47052
|
serverConfig,
|
|
47053
|
+
global,
|
|
46841
47054
|
logger
|
|
46842
47055
|
});
|
|
46843
47056
|
if (!converted) continue;
|
|
46844
|
-
for (const [key, value] of Object.entries(serverConfig)) {
|
|
46845
|
-
if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
46846
|
-
converted[key] = value;
|
|
46847
|
-
}
|
|
46848
|
-
if (serverConfig.timeout !== void 0) {
|
|
46849
|
-
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
|
|
46850
|
-
else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
|
|
46851
|
-
}
|
|
46852
|
-
if (serverConfig.disabled === true) converted.enabled = false;
|
|
46853
|
-
if (!global && "auth" in converted) {
|
|
46854
|
-
delete converted.auth;
|
|
46855
|
-
logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
|
|
46856
|
-
}
|
|
46857
|
-
if (hasUnexpandedEnvVarRef(converted)) logger?.warn(`Pi MCP: ${quoteValueForWarning(serverName)} carries an environment variable reference Pi does not expand. Pi expands only \${VAR} and $VAR in env and headers values, and only a leading ~/ in command, args and cwd.`);
|
|
46858
47057
|
namespaces.set(toPiNamespace(serverName), serverName);
|
|
46859
47058
|
result[serverName] = converted;
|
|
46860
47059
|
}
|
|
@@ -46863,16 +47062,19 @@ function convertToPiFormat({ mcpServers, global, logger }) {
|
|
|
46863
47062
|
/**
|
|
46864
47063
|
* Convert Pi's `mcpServers` back to the canonical shape: `enabled: false`
|
|
46865
47064
|
* becomes `disabled: true` and `timeout` (seconds) becomes milliseconds; every
|
|
46866
|
-
* other key is already canonical.
|
|
47065
|
+
* other key is already canonical. On a transport-less override entry an
|
|
47066
|
+
* explicit `enabled: true` is kept as `disabled: false`, since it switches on a
|
|
47067
|
+
* user-level server the user turned off.
|
|
46867
47068
|
*/
|
|
46868
47069
|
function convertFromPiFormat(mcpServers) {
|
|
46869
47070
|
const result = {};
|
|
46870
47071
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
46871
47072
|
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
46872
47073
|
const { enabled, timeout, ...rest } = serverConfig;
|
|
46873
|
-
const
|
|
47074
|
+
const keepsEnabled = enabled === true && declaresNoTransport(rest);
|
|
47075
|
+
const converted = enabled === false || keepsEnabled ? {
|
|
46874
47076
|
...rest,
|
|
46875
|
-
disabled:
|
|
47077
|
+
disabled: !enabled
|
|
46876
47078
|
} : rest;
|
|
46877
47079
|
if (typeof timeout === "number") converted.timeout = timeout * MILLISECONDS_PER_SECOND;
|
|
46878
47080
|
else if (timeout !== void 0) converted.timeout = timeout;
|
|
@@ -46948,8 +47150,11 @@ var PiMcp = class PiMcp extends ToolMcp {
|
|
|
46948
47150
|
});
|
|
46949
47151
|
}
|
|
46950
47152
|
toRulesyncMcp() {
|
|
46951
|
-
const
|
|
46952
|
-
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
47153
|
+
const { shared, toolOnly } = splitMcpServersByTransport(convertFromPiFormat(isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {}));
|
|
47154
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
47155
|
+
mcpServers: shared,
|
|
47156
|
+
...Object.keys(toolOnly).length > 0 && { pi: { mcpServers: toolOnly } }
|
|
47157
|
+
}, null, 2) });
|
|
46953
47158
|
}
|
|
46954
47159
|
validate() {
|
|
46955
47160
|
return {
|
|
@@ -64742,9 +64947,11 @@ const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
|
64742
64947
|
* the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
|
|
64743
64948
|
* permissions override.
|
|
64744
64949
|
*
|
|
64745
|
-
* Scope
|
|
64746
|
-
* a project `defaultTools` array *replaces* the global
|
|
64747
|
-
*
|
|
64950
|
+
* Scope layering is Pi's own rather than the union-merge most targets use:
|
|
64951
|
+
* a project `defaultTools` array with a plain name *replaces* the global
|
|
64952
|
+
* array, while one made only of `+name` / `-name` entries (Pi v0.99.0+) changes
|
|
64953
|
+
* the global selection instead. Entries are written verbatim, so the two
|
|
64954
|
+
* scopes are written independently and never combined by rulesync.
|
|
64748
64955
|
* Project scope writes `.pi/settings.json`; global scope writes
|
|
64749
64956
|
* `~/.pi/agent/settings.json`.
|
|
64750
64957
|
*
|
|
@@ -65404,6 +65611,13 @@ function convertPoolToRulesync({ tools, paths }) {
|
|
|
65404
65611
|
//#region src/features/permissions/qoder-permissions.ts
|
|
65405
65612
|
/** Top-level key of `.qoder/settings.json` that rulesync owns. */
|
|
65406
65613
|
const QODER_PERMISSIONS_KEY = "permissions";
|
|
65614
|
+
/**
|
|
65615
|
+
* Top-level key the `qoder` override authors: the active output style. Qoder
|
|
65616
|
+
* also reads `general.outputStyle`, but the top-level key wins, so that is the
|
|
65617
|
+
* one written and imported.
|
|
65618
|
+
* @see https://docs.qoder.com/cli/settings-reference
|
|
65619
|
+
*/
|
|
65620
|
+
const QODER_OUTPUT_STYLE_KEY = "outputStyle";
|
|
65407
65621
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
65408
65622
|
/** Prefix of the fully qualified MCP tool names (`mcp__<server>__<tool>`). */
|
|
65409
65623
|
const MCP_TOOL_PREFIX = "mcp__";
|
|
@@ -65563,9 +65777,11 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
|
|
|
65563
65777
|
* through the shared-config gateway, the sibling keys of `permissions`
|
|
65564
65778
|
* (`additionalDirectories`, `trustDirectories`) are kept, and the file is
|
|
65565
65779
|
* never deleted. `.qoder/settings.local.json` (where `/allow` and `/deny`
|
|
65566
|
-
* persist) is left to the user.
|
|
65780
|
+
* persist) is left to the user. The `qoder` override's `outputStyle` is
|
|
65781
|
+
* written to (and imported from) the top-level `outputStyle` key.
|
|
65567
65782
|
*
|
|
65568
65783
|
* @see https://docs.qoder.com/en/cli/permissions
|
|
65784
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
65569
65785
|
*/
|
|
65570
65786
|
var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
65571
65787
|
constructor(params) {
|
|
@@ -65604,11 +65820,14 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65604
65820
|
relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
|
|
65605
65821
|
});
|
|
65606
65822
|
const existingPermissions = isRecord$1(existing[QODER_PERMISSIONS_KEY]) ? existing[QODER_PERMISSIONS_KEY] : {};
|
|
65823
|
+
const config = rulesyncPermissions.getJson();
|
|
65607
65824
|
const lists = buildQoderRuleLists({
|
|
65608
|
-
config
|
|
65825
|
+
config,
|
|
65609
65826
|
existingPermissions,
|
|
65610
65827
|
logger
|
|
65611
65828
|
});
|
|
65829
|
+
const outputStyle = config.qoder?.outputStyle?.trim();
|
|
65830
|
+
if (outputStyle !== void 0) logger?.warn(`Qoder permissions: qoder.outputStyle = ${JSON.stringify(outputStyle)} was written to ${join(paths.relativeDirPath, paths.relativeFilePath)}${global ? " in the home directory" : ""}, so Qoder appends that style's instructions to the system prompt of every session it applies to.`);
|
|
65612
65831
|
const permissions = { ...existingPermissions };
|
|
65613
65832
|
for (const action of ACTIONS) if (lists[action].length > 0) permissions[action] = lists[action];
|
|
65614
65833
|
else delete permissions[action];
|
|
@@ -65620,7 +65839,10 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65620
65839
|
fileKey: sharedConfigFileKey(paths),
|
|
65621
65840
|
feature: "permissions",
|
|
65622
65841
|
existingContent,
|
|
65623
|
-
patch: {
|
|
65842
|
+
patch: {
|
|
65843
|
+
[QODER_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0,
|
|
65844
|
+
...outputStyle !== void 0 && { [QODER_OUTPUT_STYLE_KEY]: outputStyle }
|
|
65845
|
+
},
|
|
65624
65846
|
filePath
|
|
65625
65847
|
}),
|
|
65626
65848
|
validate: true,
|
|
@@ -65641,7 +65863,12 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65641
65863
|
const existing = rules[parsed.pattern];
|
|
65642
65864
|
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) rules[parsed.pattern] = action;
|
|
65643
65865
|
}
|
|
65644
|
-
|
|
65866
|
+
const outputStyle = settings[QODER_OUTPUT_STYLE_KEY];
|
|
65867
|
+
const hasOutputStyle = typeof outputStyle === "string" && outputStyle.trim() !== "";
|
|
65868
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
65869
|
+
permission,
|
|
65870
|
+
...hasOutputStyle && { qoder: { outputStyle } }
|
|
65871
|
+
}, null, 2) });
|
|
65645
65872
|
}
|
|
65646
65873
|
validate() {
|
|
65647
65874
|
return {
|
|
@@ -84689,8 +84916,8 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84689
84916
|
* Derive the subagent name from this instance's relative file path.
|
|
84690
84917
|
*
|
|
84691
84918
|
* The tool-side layout is `<name>/AGENT.md`, so the name is the parent
|
|
84692
|
-
* directory of the file.
|
|
84693
|
-
*
|
|
84919
|
+
* directory of the file. Devin also reads a flat `<name>.md`, which import
|
|
84920
|
+
* picks up too; its name is the basename without extension.
|
|
84694
84921
|
*/
|
|
84695
84922
|
getSubagentName() {
|
|
84696
84923
|
const relativeFilePath = this.getRelativeFilePath();
|
|
@@ -84783,8 +85010,9 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84783
85010
|
/**
|
|
84784
85011
|
* Subagent inside a Devin plugin bundle (`<plugin>/agents/<name>/AGENT.md`).
|
|
84785
85012
|
* Plugin subagents use the same profile format as project ones and are
|
|
84786
|
-
* exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md
|
|
84787
|
-
* the directory form is written to match the
|
|
85013
|
+
* exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md`,
|
|
85014
|
+
* which import picks up too; the directory form is written to match the
|
|
85015
|
+
* `devin` target.
|
|
84788
85016
|
*
|
|
84789
85017
|
* @see https://docs.devin.ai/cli/extensibility/plugins/overview
|
|
84790
85018
|
*/
|
|
@@ -88346,6 +88574,17 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
88346
88574
|
};
|
|
88347
88575
|
//#endregion
|
|
88348
88576
|
//#region src/features/subagents/subagents-processor.ts
|
|
88577
|
+
/**
|
|
88578
|
+
* The globs import scans for a tool: its import-only patterns when it reads
|
|
88579
|
+
* more layouts than Rulesync writes, otherwise the generation pattern.
|
|
88580
|
+
*/
|
|
88581
|
+
function importFilePatternsOf(meta) {
|
|
88582
|
+
return meta.importFilePatterns ?? meta.filePattern;
|
|
88583
|
+
}
|
|
88584
|
+
/** Whether unparseable files are left out of this import or orphan sweep. */
|
|
88585
|
+
function skipsInvalidFiles(meta, forDeletion) {
|
|
88586
|
+
return meta.skipInvalidFiles === true || !forDeletion && meta.skipInvalidImportFiles === true;
|
|
88587
|
+
}
|
|
88349
88588
|
const SubagentsProcessorToolTargetSchema = z.enum(subagentsProcessorToolTargetTuple);
|
|
88350
88589
|
/**
|
|
88351
88590
|
* Factory Map mapping tool targets to their subagent factories.
|
|
@@ -88542,7 +88781,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
88542
88781
|
supportsProject: true,
|
|
88543
88782
|
supportsSimulated: false,
|
|
88544
88783
|
supportsGlobal: true,
|
|
88545
|
-
filePattern: "*/AGENT.md"
|
|
88784
|
+
filePattern: "*/AGENT.md",
|
|
88785
|
+
importFilePatterns: ["*.md", "*/AGENT.md"],
|
|
88786
|
+
skipInvalidImportFiles: true
|
|
88546
88787
|
}
|
|
88547
88788
|
}],
|
|
88548
88789
|
["devin-plugin", {
|
|
@@ -88551,7 +88792,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
88551
88792
|
supportsProject: true,
|
|
88552
88793
|
supportsSimulated: false,
|
|
88553
88794
|
supportsGlobal: false,
|
|
88554
|
-
filePattern: "*/AGENT.md"
|
|
88795
|
+
filePattern: "*/AGENT.md",
|
|
88796
|
+
importFilePatterns: ["*.md", "*/AGENT.md"],
|
|
88797
|
+
skipInvalidImportFiles: true
|
|
88555
88798
|
}
|
|
88556
88799
|
}],
|
|
88557
88800
|
["factorydroid", {
|
|
@@ -89030,6 +89273,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
89030
89273
|
const factory = this.getFactory(this.toolTarget);
|
|
89031
89274
|
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
89032
89275
|
const roots = forDeletion ? [paths.relativeDirPath] : [paths.relativeDirPath, ...paths.importDirPaths ?? []];
|
|
89276
|
+
const filePatterns = forDeletion ? factory.meta.filePattern : importFilePatternsOf(factory.meta);
|
|
89033
89277
|
const toolSubagents = [];
|
|
89034
89278
|
const claimedRelativeFilePaths = /* @__PURE__ */ new Map();
|
|
89035
89279
|
const claimedImportIdentities = new ClaimedIdentities();
|
|
@@ -89046,7 +89290,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
89046
89290
|
this.logger.warn(`Skipping the orphan sweep for ${quoteForLog(baseDir)}; nothing under it is deleted: ${stripControlCharacters(formatError(error))}`);
|
|
89047
89291
|
continue;
|
|
89048
89292
|
}
|
|
89049
|
-
const subagentFilePaths = (await findFilesByGlobs(
|
|
89293
|
+
const subagentFilePaths = (await findFilesByGlobs(filePatterns, {
|
|
89050
89294
|
cwd: baseDir,
|
|
89051
89295
|
followSymbolicLinks: !forDeletion
|
|
89052
89296
|
})).toSorted((left, right) => compareRelativePathsShallowestFirst(relative(baseDir, left), relative(baseDir, right)));
|
|
@@ -89060,7 +89304,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
89060
89304
|
})));
|
|
89061
89305
|
ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
|
|
89062
89306
|
}
|
|
89063
|
-
const parsedFiles = factory.meta
|
|
89307
|
+
const parsedFiles = skipsInvalidFiles(factory.meta, forDeletion) ? (await Promise.all(ownedFilePaths.map(async (path) => {
|
|
89064
89308
|
try {
|
|
89065
89309
|
return {
|
|
89066
89310
|
path,
|
|
@@ -91936,6 +92180,85 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
91936
92180
|
}
|
|
91937
92181
|
};
|
|
91938
92182
|
//#endregion
|
|
92183
|
+
//#region src/features/rules/codebuff-rule.ts
|
|
92184
|
+
/**
|
|
92185
|
+
* Rule generator for Codebuff (Freebuff), global scope only.
|
|
92186
|
+
*
|
|
92187
|
+
* Codebuff reads one user-level instructions file from the home directory:
|
|
92188
|
+
* the first hidden file matching `AGENTS.md` or `CLAUDE.md` (case-insensitive,
|
|
92189
|
+
* in that priority order), so the root rule is written to `~/.AGENTS.md`. There
|
|
92190
|
+
* is no user-level rules directory, so non-root rules fold into that file.
|
|
92191
|
+
*
|
|
92192
|
+
* Project scope is not supported here: Codebuff reads the project-root
|
|
92193
|
+
* `AGENTS.md`, which the `agentsmd` target already writes. The RulesProcessor
|
|
92194
|
+
* keeps this target out of project mode (`supportsProject: false`), so the
|
|
92195
|
+
* settable paths are the home-directory ones regardless of `global`.
|
|
92196
|
+
*
|
|
92197
|
+
* @see https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/run-state.ts
|
|
92198
|
+
* @see https://github.com/CodebuffAI/freebuff/blob/main/common/src/constants/knowledge.ts
|
|
92199
|
+
*/
|
|
92200
|
+
var CodebuffRule = class CodebuffRule extends ToolRule {
|
|
92201
|
+
static getSettablePaths(_options = {}) {
|
|
92202
|
+
return { root: {
|
|
92203
|
+
relativeDirPath: ".",
|
|
92204
|
+
relativeFilePath: CODEBUFF_GLOBAL_RULE_FILE_NAME
|
|
92205
|
+
} };
|
|
92206
|
+
}
|
|
92207
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
92208
|
+
const { root } = this.getSettablePaths({ global: true });
|
|
92209
|
+
if (relativeFilePath !== root.relativeFilePath) throw new Error(`CodebuffRule only supports the root rule: ${relativeFilePath}`);
|
|
92210
|
+
const fileContent = await readFileContent(join(outputRoot, root.relativeDirPath, root.relativeFilePath));
|
|
92211
|
+
return new CodebuffRule({
|
|
92212
|
+
outputRoot,
|
|
92213
|
+
relativeDirPath: root.relativeDirPath,
|
|
92214
|
+
relativeFilePath: root.relativeFilePath,
|
|
92215
|
+
fileContent,
|
|
92216
|
+
validate,
|
|
92217
|
+
root: true
|
|
92218
|
+
});
|
|
92219
|
+
}
|
|
92220
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
92221
|
+
const { root } = this.getSettablePaths({ global: true });
|
|
92222
|
+
const frontmatter = rulesyncRule.getFrontmatter();
|
|
92223
|
+
return new CodebuffRule({
|
|
92224
|
+
outputRoot,
|
|
92225
|
+
relativeDirPath: root.relativeDirPath,
|
|
92226
|
+
relativeFilePath: root.relativeFilePath,
|
|
92227
|
+
fileContent: rulesyncRule.getBody(),
|
|
92228
|
+
validate,
|
|
92229
|
+
root: frontmatter.root ?? false,
|
|
92230
|
+
description: frontmatter.description,
|
|
92231
|
+
globs: frontmatter.globs
|
|
92232
|
+
});
|
|
92233
|
+
}
|
|
92234
|
+
toRulesyncRule() {
|
|
92235
|
+
return this.toRulesyncRuleDefault();
|
|
92236
|
+
}
|
|
92237
|
+
validate() {
|
|
92238
|
+
return {
|
|
92239
|
+
success: true,
|
|
92240
|
+
error: null
|
|
92241
|
+
};
|
|
92242
|
+
}
|
|
92243
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
92244
|
+
const { root } = this.getSettablePaths({ global: true });
|
|
92245
|
+
return new CodebuffRule({
|
|
92246
|
+
outputRoot,
|
|
92247
|
+
relativeDirPath,
|
|
92248
|
+
relativeFilePath,
|
|
92249
|
+
fileContent: "",
|
|
92250
|
+
validate: false,
|
|
92251
|
+
root: relativeDirPath === root.relativeDirPath && relativeFilePath === root.relativeFilePath
|
|
92252
|
+
});
|
|
92253
|
+
}
|
|
92254
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
92255
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
92256
|
+
rulesyncRule,
|
|
92257
|
+
toolTarget: "codebuff"
|
|
92258
|
+
});
|
|
92259
|
+
}
|
|
92260
|
+
};
|
|
92261
|
+
//#endregion
|
|
91939
92262
|
//#region src/features/rules/codewhale-rule.ts
|
|
91940
92263
|
/**
|
|
91941
92264
|
* Rule generator for Codewhale.
|
|
@@ -93573,7 +93896,7 @@ var DshRule = class extends NestedAgentsmdRule {
|
|
|
93573
93896
|
* `getExtraFixedFiles`): Droid loads "direct `.md` children of each
|
|
93574
93897
|
* `output-styles` directory".
|
|
93575
93898
|
*/
|
|
93576
|
-
const OUTPUT_STYLE_FILE_GLOB = "*.md";
|
|
93899
|
+
const OUTPUT_STYLE_FILE_GLOB$1 = "*.md";
|
|
93577
93900
|
/**
|
|
93578
93901
|
* Rule generator for Factory Droid.
|
|
93579
93902
|
*
|
|
@@ -93695,7 +94018,7 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
93695
94018
|
static getExtraFixedFiles({ global = false } = {}) {
|
|
93696
94019
|
return [...this.getChannelPaths({ global }).map(({ path }) => path), {
|
|
93697
94020
|
relativeDirPath: this.getOutputStylesDirPath(),
|
|
93698
|
-
relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
|
|
94021
|
+
relativeFilePath: OUTPUT_STYLE_FILE_GLOB$1,
|
|
93699
94022
|
importOnly: true
|
|
93700
94023
|
}];
|
|
93701
94024
|
}
|
|
@@ -95550,6 +95873,12 @@ const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set([
|
|
|
95550
95873
|
* Any other mode is kept in the `qoder` section so it survives a round trip.
|
|
95551
95874
|
*/
|
|
95552
95875
|
const INFERABLE_TRIGGERS = /* @__PURE__ */ new Set(["always_on", "glob"]);
|
|
95876
|
+
/**
|
|
95877
|
+
* The file-name glob `getExtraFixedFiles` returns for the output-styles
|
|
95878
|
+
* directory, so import enumerates every style in it (never deletion: the
|
|
95879
|
+
* directory also holds hand-written styles).
|
|
95880
|
+
*/
|
|
95881
|
+
const OUTPUT_STYLE_FILE_GLOB = "*.md";
|
|
95553
95882
|
function normalizeGlobList(value) {
|
|
95554
95883
|
if (value === void 0) return [];
|
|
95555
95884
|
return (typeof value === "string" ? splitBraceAwareList(value) : value).map((glob) => glob.trim()).filter((glob) => glob !== "");
|
|
@@ -95583,22 +95912,59 @@ function resolveTrigger(frontmatter) {
|
|
|
95583
95912
|
* A `qoder.trigger` (for example `model_decision` or `manual`) always takes
|
|
95584
95913
|
* precedence and round-trips through import.
|
|
95585
95914
|
*
|
|
95915
|
+
* A non-root rule with `qoder.channel: output-style` becomes a custom output
|
|
95916
|
+
* style instead, `.qoder/output-styles/<rule file name>.md` (both scopes),
|
|
95917
|
+
* with the rule's `description` and optional `qoder.name` as the style's
|
|
95918
|
+
* `description` / `name` frontmatter.
|
|
95919
|
+
*
|
|
95586
95920
|
* @see https://docs.qoder.com/en/cli/04-extending-qoder-cli/memory
|
|
95921
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
95587
95922
|
*/
|
|
95588
95923
|
var QoderRule = class QoderRule extends ToolRule {
|
|
95589
95924
|
frontmatter;
|
|
95590
95925
|
body;
|
|
95591
|
-
|
|
95926
|
+
outputStyle;
|
|
95927
|
+
constructor({ frontmatter, body, outputStyle = false, ...rest }) {
|
|
95592
95928
|
if (rest.validate) {
|
|
95593
95929
|
const result = QoderRuleFrontmatterSchema.safeParse(frontmatter);
|
|
95594
95930
|
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
95595
95931
|
}
|
|
95596
95932
|
super({
|
|
95597
95933
|
...rest,
|
|
95598
|
-
fileContent: rest.root ? body : stringifyFrontmatter(body, frontmatter)
|
|
95934
|
+
fileContent: rest.root || outputStyle && Object.keys(frontmatter).length === 0 ? body : stringifyFrontmatter(body, frontmatter)
|
|
95599
95935
|
});
|
|
95600
95936
|
this.frontmatter = frontmatter;
|
|
95601
95937
|
this.body = body;
|
|
95938
|
+
this.outputStyle = outputStyle;
|
|
95939
|
+
}
|
|
95940
|
+
/**
|
|
95941
|
+
* Whether the given path is a custom output style: a direct `.md` child of
|
|
95942
|
+
* `.qoder/output-styles/`, at either scope.
|
|
95943
|
+
*/
|
|
95944
|
+
static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
|
|
95945
|
+
return relativeDirPath === QODER_OUTPUT_STYLES_DIR_PATH && extname(relativeFilePath) === ".md" && basename(relativeFilePath) === relativeFilePath;
|
|
95946
|
+
}
|
|
95947
|
+
/** Whether a rule opts into the `output-style` channel (non-root only). */
|
|
95948
|
+
static isOutputStyleRule(rulesyncRule) {
|
|
95949
|
+
const frontmatter = rulesyncRule.getFrontmatter();
|
|
95950
|
+
return !frontmatter.root && frontmatter.qoder?.channel === "output-style";
|
|
95951
|
+
}
|
|
95952
|
+
/**
|
|
95953
|
+
* The output-styles directory, enumerated for import only: it also holds
|
|
95954
|
+
* styles users write by hand (including personal ones in
|
|
95955
|
+
* `~/.qoder/output-styles/`) that rulesync cannot tell from its own, so
|
|
95956
|
+
* `--delete` leaves it alone.
|
|
95957
|
+
*/
|
|
95958
|
+
static getExtraFixedFiles(_params = {}) {
|
|
95959
|
+
return [{
|
|
95960
|
+
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
95961
|
+
relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
|
|
95962
|
+
importOnly: true
|
|
95963
|
+
}];
|
|
95964
|
+
}
|
|
95965
|
+
/** Qoder loads output styles itself; they are not rules to reference. */
|
|
95966
|
+
isExcludedFromRootReferences() {
|
|
95967
|
+
return this.outputStyle;
|
|
95602
95968
|
}
|
|
95603
95969
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
95604
95970
|
return {
|
|
@@ -95611,6 +95977,23 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95611
95977
|
}
|
|
95612
95978
|
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
95613
95979
|
const paths = this.getSettablePaths({ global });
|
|
95980
|
+
if (this.isOutputStylePath({
|
|
95981
|
+
relativeDirPath,
|
|
95982
|
+
relativeFilePath
|
|
95983
|
+
})) {
|
|
95984
|
+
const filePath = join(outputRoot, QODER_OUTPUT_STYLES_DIR_PATH, relativeFilePath);
|
|
95985
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
95986
|
+
return new QoderRule({
|
|
95987
|
+
outputRoot,
|
|
95988
|
+
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
95989
|
+
relativeFilePath,
|
|
95990
|
+
frontmatter,
|
|
95991
|
+
body: body.trim(),
|
|
95992
|
+
validate,
|
|
95993
|
+
root: false,
|
|
95994
|
+
outputStyle: true
|
|
95995
|
+
});
|
|
95996
|
+
}
|
|
95614
95997
|
if (relativeDirPath !== void 0 ? relativeDirPath !== QODER_RULES_DIR_PATH : relativeFilePath === paths.root.relativeFilePath) {
|
|
95615
95998
|
const rootDirPath = relativeDirPath ?? paths.root.relativeDirPath;
|
|
95616
95999
|
const fileContent = await readFileContent(join(outputRoot, rootDirPath, paths.root.relativeFilePath));
|
|
@@ -95643,6 +96026,11 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95643
96026
|
const root = rulesyncFrontmatter.root ?? false;
|
|
95644
96027
|
const paths = this.getSettablePaths({ global });
|
|
95645
96028
|
const body = rulesyncRule.getBody();
|
|
96029
|
+
if (this.isOutputStyleRule(rulesyncRule)) return this.buildOutputStyle({
|
|
96030
|
+
outputRoot,
|
|
96031
|
+
rulesyncRule,
|
|
96032
|
+
validate
|
|
96033
|
+
});
|
|
95646
96034
|
if (root) return new QoderRule({
|
|
95647
96035
|
outputRoot,
|
|
95648
96036
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -95662,8 +96050,32 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95662
96050
|
root: false
|
|
95663
96051
|
});
|
|
95664
96052
|
}
|
|
96053
|
+
/**
|
|
96054
|
+
* Write an `output-style` rule as `.qoder/output-styles/<name>.md`. The file
|
|
96055
|
+
* name is the rule's own, so a rule nested under `.rulesync/rules/` is
|
|
96056
|
+
* refused: Qoder reads only the files directly in `output-styles/`.
|
|
96057
|
+
*/
|
|
96058
|
+
static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
|
|
96059
|
+
const { description, qoder } = rulesyncRule.getFrontmatter();
|
|
96060
|
+
const sourcePath = rulesyncRule.getRelativeFilePath();
|
|
96061
|
+
if (basename(sourcePath) !== sourcePath) throw new Error(`Rule '${sourcePath}' sets qoder.channel: output-style but is nested in a subdirectory: Qoder reads only the files directly in .qoder/output-styles/, so move it to the top of ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`);
|
|
96062
|
+
const name = qoder?.name;
|
|
96063
|
+
return new QoderRule({
|
|
96064
|
+
outputRoot,
|
|
96065
|
+
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
96066
|
+
relativeFilePath: `${basename(sourcePath, extname(sourcePath))}.md`,
|
|
96067
|
+
frontmatter: {
|
|
96068
|
+
...name !== void 0 && { name },
|
|
96069
|
+
...description !== void 0 && { description }
|
|
96070
|
+
},
|
|
96071
|
+
body: rulesyncRule.getBody(),
|
|
96072
|
+
validate,
|
|
96073
|
+
root: false,
|
|
96074
|
+
outputStyle: true
|
|
96075
|
+
});
|
|
96076
|
+
}
|
|
95665
96077
|
static buildFrontmatter(rulesyncFrontmatter, relativeFilePath) {
|
|
95666
|
-
const { trigger: storedTrigger, glob: storedGlob, ...section } = rulesyncFrontmatter.qoder ?? {};
|
|
96078
|
+
const { trigger: storedTrigger, glob: storedGlob, channel: _channel, name: _name, ...section } = rulesyncFrontmatter.qoder ?? {};
|
|
95667
96079
|
const specificGlobs = normalizeGlobList(storedGlob ?? rulesyncFrontmatter.globs).filter((glob) => !UNIVERSAL_GLOBS$1.has(glob));
|
|
95668
96080
|
let trigger = storedTrigger ?? (specificGlobs.length > 0 ? "glob" : "always_on");
|
|
95669
96081
|
const description = section.description ?? rulesyncFrontmatter.description;
|
|
@@ -95681,6 +96093,25 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95681
96093
|
}
|
|
95682
96094
|
toRulesyncRule() {
|
|
95683
96095
|
if (this.isRoot()) return this.toRulesyncRuleDefault();
|
|
96096
|
+
if (this.outputStyle) {
|
|
96097
|
+
const { name, description } = this.frontmatter;
|
|
96098
|
+
return new RulesyncRule({
|
|
96099
|
+
outputRoot: this.getOutputRoot(),
|
|
96100
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
96101
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
96102
|
+
frontmatter: {
|
|
96103
|
+
root: false,
|
|
96104
|
+
targets: ["qoder"],
|
|
96105
|
+
...typeof description === "string" && { description },
|
|
96106
|
+
qoder: {
|
|
96107
|
+
channel: "output-style",
|
|
96108
|
+
...typeof name === "string" && name.trim() !== "" && { name }
|
|
96109
|
+
}
|
|
96110
|
+
},
|
|
96111
|
+
body: this.body,
|
|
96112
|
+
validate: true
|
|
96113
|
+
});
|
|
96114
|
+
}
|
|
95684
96115
|
const trigger = resolveTrigger(this.frontmatter);
|
|
95685
96116
|
const { trigger: _trigger, alwaysApply: _alwaysApply, description, glob, paths, ...unmapped } = this.frontmatter;
|
|
95686
96117
|
const fileGlobs = normalizeGlobList(glob ?? paths);
|
|
@@ -95729,6 +96160,10 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95729
96160
|
return this.body;
|
|
95730
96161
|
}
|
|
95731
96162
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
96163
|
+
const outputStyle = this.isOutputStylePath({
|
|
96164
|
+
relativeDirPath,
|
|
96165
|
+
relativeFilePath
|
|
96166
|
+
});
|
|
95732
96167
|
return new QoderRule({
|
|
95733
96168
|
outputRoot,
|
|
95734
96169
|
relativeDirPath,
|
|
@@ -95736,7 +96171,8 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95736
96171
|
frontmatter: {},
|
|
95737
96172
|
body: "",
|
|
95738
96173
|
validate: false,
|
|
95739
|
-
root: relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md"
|
|
96174
|
+
root: !outputStyle && relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md",
|
|
96175
|
+
outputStyle
|
|
95740
96176
|
});
|
|
95741
96177
|
}
|
|
95742
96178
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -97423,6 +97859,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
97423
97859
|
localRootFileName: CODEBUDDY_LOCAL_RULE_FILE_NAME
|
|
97424
97860
|
}
|
|
97425
97861
|
}],
|
|
97862
|
+
["codebuff", {
|
|
97863
|
+
class: CodebuffRule,
|
|
97864
|
+
meta: {
|
|
97865
|
+
extension: "md",
|
|
97866
|
+
supportsProject: false,
|
|
97867
|
+
supportsGlobal: true,
|
|
97868
|
+
ruleDiscoveryMode: "auto",
|
|
97869
|
+
collisionPolicy: "fold"
|
|
97870
|
+
}
|
|
97871
|
+
}],
|
|
97426
97872
|
["codewhale", {
|
|
97427
97873
|
class: CodewhaleRule,
|
|
97428
97874
|
meta: {
|
|
@@ -97853,7 +98299,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
97853
98299
|
}]
|
|
97854
98300
|
]);
|
|
97855
98301
|
const allToolTargetKeys = [...toolRuleFactories.keys()];
|
|
97856
|
-
const rulesProcessorToolTargets = allToolTargetKeys
|
|
98302
|
+
const rulesProcessorToolTargets = allToolTargetKeys.filter((target) => {
|
|
98303
|
+
return toolRuleFactories.get(target)?.meta.supportsProject !== false;
|
|
98304
|
+
});
|
|
97857
98305
|
const rulesProcessorToolTargetsGlobal = allToolTargetKeys.filter((target) => {
|
|
97858
98306
|
return toolRuleFactories.get(target)?.meta.supportsGlobal ?? false;
|
|
97859
98307
|
});
|
|
@@ -97932,7 +98380,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
97932
98380
|
const factory = this.getFactory(this.toolTarget);
|
|
97933
98381
|
const { meta } = factory;
|
|
97934
98382
|
this.warnForIncludeRootOption(meta);
|
|
97935
|
-
this.
|
|
98383
|
+
this.warnForOutputStyleRuleOptions({
|
|
97936
98384
|
rules: alignedRules,
|
|
97937
98385
|
factory
|
|
97938
98386
|
});
|
|
@@ -98166,22 +98614,25 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
98166
98614
|
}) !== "auto") this.logger.warn("The rules option `ruleDiscoveryMode` has no effect with `includeRoot: false` on claudecode: the rule references it adds are written into CLAUDE.md, which is not generated.");
|
|
98167
98615
|
}
|
|
98168
98616
|
/**
|
|
98169
|
-
* Warn about `factorydroid` frontmatter that does not do what it
|
|
98170
|
-
* it does: an `output-style` rule that also targets other tools
|
|
98171
|
-
* as an ordinary rule (so presentation instructions end up in
|
|
98172
|
-
* rules), and
|
|
98173
|
-
* Checked while generating for
|
|
98617
|
+
* Warn about `factorydroid` / `qoder` frontmatter that does not do what it
|
|
98618
|
+
* looks like it does: an `output-style` rule that also targets other tools
|
|
98619
|
+
* reaches them as an ordinary rule (so presentation instructions end up in
|
|
98620
|
+
* their project rules), and `<tool>.name` is read only with
|
|
98621
|
+
* `channel: output-style`. Checked while generating for that tool only, so
|
|
98622
|
+
* each warning prints once.
|
|
98174
98623
|
*/
|
|
98175
|
-
|
|
98176
|
-
|
|
98624
|
+
warnForOutputStyleRuleOptions({ rules, factory }) {
|
|
98625
|
+
const toolTarget = this.toolTarget;
|
|
98626
|
+
if (toolTarget !== "factorydroid" && toolTarget !== "qoder") return;
|
|
98177
98627
|
for (const rule of rules) {
|
|
98178
98628
|
const frontmatter = rule.getFrontmatter();
|
|
98179
|
-
|
|
98180
|
-
|
|
98629
|
+
const section = frontmatter[toolTarget];
|
|
98630
|
+
if (frontmatter.root || !section || !factory.class.isTargetedByRulesyncRule(rule)) continue;
|
|
98631
|
+
const { channel, name } = section;
|
|
98181
98632
|
const fileName = stripControlCharacters(rule.getRelativeFilePath());
|
|
98182
98633
|
const targets = frontmatter.targets ?? ["*"];
|
|
98183
|
-
if (channel === "output-style" && targets.some((target) => target !==
|
|
98184
|
-
if (channel !== "output-style" && name !== void 0) this.logger.warn(`${fileName} sets
|
|
98634
|
+
if (channel === "output-style" && targets.some((target) => target !== toolTarget)) this.logger.warn(`${fileName} sets ${toolTarget}.channel: output-style but also targets other tools, which receive it as an ordinary rule. Set \`targets: ["${toolTarget}"]\` to keep the output style to ${toolTarget === "qoder" ? "Qoder" : "Factory Droid"}.`);
|
|
98635
|
+
if (channel !== "output-style" && name !== void 0) this.logger.warn(`${fileName} sets ${toolTarget}.name, which is read only with ${toolTarget}.channel: output-style; ignoring it.`);
|
|
98185
98636
|
}
|
|
98186
98637
|
}
|
|
98187
98638
|
/**
|
|
@@ -101926,6 +102377,6 @@ async function importChecksCore(params) {
|
|
|
101926
102377
|
return writtenCount;
|
|
101927
102378
|
}
|
|
101928
102379
|
//#endregion
|
|
101929
|
-
export { RulesyncHooks as $,
|
|
102380
|
+
export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, ALL_FEATURES as Bn, getFileSize as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_MCP_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, hasDeceptiveHiddenCharacters as Gn, isSymlink as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, DEPRECATED_FEATURE_REPLACEMENTS as Hn, hasExecutableBit as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as In, directoryExists as It, RulesyncRule as J, stripControlCharacters as Jn, listSubdirectoryNames as Jt, RulesyncSkill as K, hasEnclosingMarkOutsideKeycap as Kn, listDirectoryEntryNames as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_USER_CONFIG_DIR_NAME as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_RELATIVE_DIR_PATH as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_RULES_RELATIVE_DIR_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_MCP_SCHEMA_URL as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectory as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, formatError as Un, isFileNotFoundError as Ut, AUGMENTCODE_DIR as V, ALL_FEATURES_WITH_WILDCARD as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, truncateText as Wn, isFileSystemError as Wt, RulesyncPermissions as X, stripHiddenCharacters as Xn, readFileContent as Xt, RulesyncRuleFrontmatterSchema as Y, stripControlCharactersKeepingLineFeeds as Yn, pathEscapesRoot as Yt, RulesyncMcp as Z, readFileContentOrNull as Zt, LETTACODE_DIR as _, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, runWithDirectoryRollback as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_CONFIG_RELATIVE_FILE_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, writeFileBuffer as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ALL_TOOL_TARGETS_WITH_WILDCARD as dn, ConfigResolver as dt, removeFile as en, getRulesyncSourceCandidates as et, displayWidthOf as f, PACKAGING_TOOL_TARGETS as fn, mergeInputRootConfigs as ft, HooksProcessor as g, MAX_FILE_SIZE as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, FEATURE_SOURCE_TREE_ENTRIES as hn, ConfigFileSchema as ht, inspectInputRoots as i, resolvedPathEscapesRoot as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_PERMISSIONS_SCHEMA_URL as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as kn, ErrorCodes as kt, SubagentsProcessor as l, writeFileContent as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, CURATED_RULES_FEATURE_SUBDIR as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, removeTempDirectory as nn, parseJsonc as nt, convertFromTool as o, splitPathSegments as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, ToolTargetSchema as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, quoteForLog as qn, listFilePathsRecursively as qt, generate as r, resolvePath as rn, RulesyncCommand as rt, isPackagingToolTarget as s, toPosixPath as sn, stringifyFrontmatter as st, importFromTool as t, removeFileStrict as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, ALL_TOOL_TARGETS as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_CHECKS_RELATIVE_DIR_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_HOOKS_RELATIVE_FILE_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CONFIG_SCHEMA_URL as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as yn, findControlCharacter as yt, QWENCODE_DIR as z, parseCommaSeparatedList as zn, findFilesByGlobs as zt };
|
|
101930
102381
|
|
|
101931
|
-
//# sourceMappingURL=import-
|
|
102382
|
+
//# sourceMappingURL=import-DFwX4TTE.js.map
|