rulesync 33.0.0 → 34.0.1
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
|
@@ -408,7 +408,11 @@ const DeprecatedIgnoreFeatureSchema = zod_mini.z.literal("ignore").check((0, zod
|
|
|
408
408
|
const FeatureSchema = zod_mini.z.union([zod_mini.z.enum(ACTIVE_FEATURES), DeprecatedIgnoreFeatureSchema]);
|
|
409
409
|
zod_mini.z.array(FeatureSchema);
|
|
410
410
|
const FeatureWithWildcardSchema = zod_mini.z.union([zod_mini.z.enum([...ACTIVE_FEATURES, "*"]), DeprecatedIgnoreFeatureSchema]);
|
|
411
|
-
const GitignoreDestinationSchema = zod_mini.z.enum([
|
|
411
|
+
const GitignoreDestinationSchema = zod_mini.z.enum([
|
|
412
|
+
"gitignore",
|
|
413
|
+
"gitattributes",
|
|
414
|
+
"gitexclude"
|
|
415
|
+
]);
|
|
412
416
|
const FlattenedCommandNamingSchema = zod_mini.z.enum(["basename", "path"]);
|
|
413
417
|
const FeatureOptionsSchema = zod_mini.z.record(zod_mini.z.string(), zod_mini.z.unknown());
|
|
414
418
|
const FeatureValueSchema = zod_mini.z.union([
|
|
@@ -453,36 +457,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
453
457
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
454
458
|
//#endregion
|
|
455
459
|
//#region src/constants/rulesync-paths.ts
|
|
456
|
-
const { join: join$
|
|
460
|
+
const { join: join$418 } = node_path.posix;
|
|
457
461
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
458
462
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
459
463
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
460
464
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
461
465
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
462
466
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
463
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
467
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$418(RULES_FEATURE_SUBDIR, ".curated");
|
|
464
468
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
465
469
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
466
470
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
467
471
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
468
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
469
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
470
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
471
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
472
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
473
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
474
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
475
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
476
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
477
|
-
join$
|
|
478
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
479
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
472
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$418(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
473
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
475
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
477
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
478
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
479
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
480
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
481
|
+
join$418(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
482
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
483
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
480
484
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
481
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
485
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
482
486
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
483
487
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
484
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
485
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
488
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
489
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
486
490
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
487
491
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
488
492
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -528,6 +532,7 @@ const rulesProcessorToolTargetTuple = [
|
|
|
528
532
|
"claudecode-legacy",
|
|
529
533
|
"cline",
|
|
530
534
|
"codebuddy",
|
|
535
|
+
"codebuff",
|
|
531
536
|
"codewhale",
|
|
532
537
|
"codexcli",
|
|
533
538
|
"commandcode",
|
|
@@ -1407,6 +1412,13 @@ async function getFileSize(filepath) {
|
|
|
1407
1412
|
throw new Error(`Failed to get file size for "${filepath}": ${formatError(error)}`, { cause: error });
|
|
1408
1413
|
}
|
|
1409
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Whether a file carries any execute bit. Windows reports none, so a file
|
|
1417
|
+
* there is never seen as executable.
|
|
1418
|
+
*/
|
|
1419
|
+
async function hasExecutableBit(filepath) {
|
|
1420
|
+
return ((await (0, node_fs_promises.stat)(filepath)).mode & 73) !== 0;
|
|
1421
|
+
}
|
|
1410
1422
|
async function isSymlink(filepath) {
|
|
1411
1423
|
try {
|
|
1412
1424
|
return (await (0, node_fs_promises.lstat)(filepath)).isSymbolicLink();
|
|
@@ -3179,6 +3191,9 @@ var Config = class Config {
|
|
|
3179
3191
|
const featureValue = value[feature];
|
|
3180
3192
|
if (featureValue && typeof featureValue === "object" && isFeatureValueEnabled(featureValue)) return featureValue;
|
|
3181
3193
|
}
|
|
3194
|
+
getRootGitignoreDestination() {
|
|
3195
|
+
return this.gitignoreDestination;
|
|
3196
|
+
}
|
|
3182
3197
|
getGitignoreDestination(target, feature) {
|
|
3183
3198
|
const rootLevel = this.gitignoreDestination;
|
|
3184
3199
|
if (!isRulesyncConfigTargetsObject(this.targets)) return rootLevel;
|
|
@@ -3196,7 +3211,8 @@ var Config = class Config {
|
|
|
3196
3211
|
return toolLevel ?? rootLevel;
|
|
3197
3212
|
}
|
|
3198
3213
|
static parseGitignoreDestination(value) {
|
|
3199
|
-
|
|
3214
|
+
const result = GitignoreDestinationSchema.safeParse(value);
|
|
3215
|
+
return result.success ? result.data : void 0;
|
|
3200
3216
|
}
|
|
3201
3217
|
/**
|
|
3202
3218
|
* Check if per-target features configuration is being used.
|
|
@@ -5266,12 +5282,20 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
5266
5282
|
"sessionEnd"
|
|
5267
5283
|
];
|
|
5268
5284
|
/**
|
|
5269
|
-
* Hook events supported by the GitLab Duo CLI.
|
|
5270
|
-
*
|
|
5271
|
-
*
|
|
5285
|
+
* Hook events supported by the GitLab Duo CLI. `SessionStart` is documented;
|
|
5286
|
+
* `PreToolUse` and `SessionEnd` ship in gitlab-lsp (`HOOK_EVENT_NAMES`, v9.28.0+)
|
|
5287
|
+
* but are not on docs.gitlab.com yet. The optional regex matcher filters on the
|
|
5288
|
+
* session source (`startup`, `resume`) for `SessionStart`, on `tool_name` for
|
|
5289
|
+
* `PreToolUse`, and on the end reason for `SessionEnd`. Only `type: "command"`
|
|
5290
|
+
* hooks are supported.
|
|
5272
5291
|
* @see https://docs.gitlab.com/user/gitlab_duo_cli/customize/
|
|
5292
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/main/packages/core/hooks/src/schema.ts
|
|
5273
5293
|
*/
|
|
5274
|
-
const GITLABDUO_HOOK_EVENTS = [
|
|
5294
|
+
const GITLABDUO_HOOK_EVENTS = [
|
|
5295
|
+
"sessionStart",
|
|
5296
|
+
"preToolUse",
|
|
5297
|
+
"sessionEnd"
|
|
5298
|
+
];
|
|
5275
5299
|
/**
|
|
5276
5300
|
* Hook events supported by Qwen Code.
|
|
5277
5301
|
*
|
|
@@ -5612,7 +5636,10 @@ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
5612
5636
|
* or `agent` hooks with a `timeout` in seconds. The hooks reference documents ten events; the
|
|
5613
5637
|
* plugins reference lists the full set, stating that plugin hooks "respond to
|
|
5614
5638
|
* the same lifecycle events as user-defined hooks", so every event listed
|
|
5615
|
-
* there is supported here.
|
|
5639
|
+
* there is supported here. The hooks reference also names `Setup`
|
|
5640
|
+
* (bootstrap/maintenance), and the published package's settings schema
|
|
5641
|
+
* (`@tencent-ai/codebuddy-code` 2.167.0) accepts `UserPromptExpansion`, whose
|
|
5642
|
+
* matcher filters on `command_name` as in Claude Code.
|
|
5616
5643
|
*
|
|
5617
5644
|
* @see https://www.codebuddy.ai/docs/cli/hooks
|
|
5618
5645
|
* @see https://www.codebuddy.ai/docs/cli/plugins-reference
|
|
@@ -5643,7 +5670,9 @@ const CODEBUDDY_HOOK_EVENTS = [
|
|
|
5643
5670
|
"postCompact",
|
|
5644
5671
|
"elicitation",
|
|
5645
5672
|
"elicitationResult",
|
|
5646
|
-
"sessionEnd"
|
|
5673
|
+
"sessionEnd",
|
|
5674
|
+
"setup",
|
|
5675
|
+
"userPromptExpansion"
|
|
5647
5676
|
];
|
|
5648
5677
|
/**
|
|
5649
5678
|
* CodeBuddy Code events that fire on every occurrence and take no `matcher`.
|
|
@@ -5692,7 +5721,9 @@ const CANONICAL_TO_CODEBUDDY_EVENT_NAMES = {
|
|
|
5692
5721
|
postCompact: "PostCompact",
|
|
5693
5722
|
elicitation: "Elicitation",
|
|
5694
5723
|
elicitationResult: "ElicitationResult",
|
|
5695
|
-
sessionEnd: "SessionEnd"
|
|
5724
|
+
sessionEnd: "SessionEnd",
|
|
5725
|
+
setup: "Setup",
|
|
5726
|
+
userPromptExpansion: "UserPromptExpansion"
|
|
5696
5727
|
};
|
|
5697
5728
|
const CODEBUDDY_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CODEBUDDY_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
5698
5729
|
/**
|
|
@@ -6546,7 +6577,11 @@ const KIRO_IDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
|
|
|
6546
6577
|
/**
|
|
6547
6578
|
* Map canonical camelCase event names to GitLab Duo CLI PascalCase.
|
|
6548
6579
|
*/
|
|
6549
|
-
const CANONICAL_TO_GITLABDUO_EVENT_NAMES = {
|
|
6580
|
+
const CANONICAL_TO_GITLABDUO_EVENT_NAMES = {
|
|
6581
|
+
sessionStart: "SessionStart",
|
|
6582
|
+
preToolUse: "PreToolUse",
|
|
6583
|
+
sessionEnd: "SessionEnd"
|
|
6584
|
+
};
|
|
6550
6585
|
/**
|
|
6551
6586
|
* Map GitLab Duo CLI PascalCase event names to canonical camelCase.
|
|
6552
6587
|
*/
|
|
@@ -8287,6 +8322,48 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8287
8322
|
]))
|
|
8288
8323
|
});
|
|
8289
8324
|
/**
|
|
8325
|
+
* Tool-scoped override block for Qoder. `outputStyle` is written to the
|
|
8326
|
+
* top-level `outputStyle` key of `.qoder/settings.json` (project) /
|
|
8327
|
+
* `~/.qoder/settings.json` (global): the name of the active output style, a
|
|
8328
|
+
* custom style from `.qoder/output-styles/` (see the `qoder.channel:
|
|
8329
|
+
* output-style` rule option) or a `plugin-name:style-name` reference. The
|
|
8330
|
+
* shared `permission` block continues to drive `permissions.allow`/`ask`/`deny`.
|
|
8331
|
+
*
|
|
8332
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
8333
|
+
* @see https://docs.qoder.com/cli/settings-reference
|
|
8334
|
+
* @example
|
|
8335
|
+
* { "outputStyle": "concise-review" }
|
|
8336
|
+
*/
|
|
8337
|
+
const QoderPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8338
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8339
|
+
outputStyle: zod_mini.z.optional(zod_mini.z.string().check(zod_mini.z.trim(), zod_mini.z.minLength(1)))
|
|
8340
|
+
});
|
|
8341
|
+
/**
|
|
8342
|
+
* One native Factory Droid permission rule. `match.prefix` positions are either
|
|
8343
|
+
* a single token or a non-empty list of alternative tokens.
|
|
8344
|
+
*
|
|
8345
|
+
* @see https://docs.factory.com/autonomy-and-safety/permission-rules
|
|
8346
|
+
*/
|
|
8347
|
+
const FactorydroidPermissionRuleSchema = zod_mini.z.looseObject({
|
|
8348
|
+
id: zod_mini.z.string(),
|
|
8349
|
+
decision: zod_mini.z.enum([
|
|
8350
|
+
"allow",
|
|
8351
|
+
"ask",
|
|
8352
|
+
"block"
|
|
8353
|
+
]),
|
|
8354
|
+
match: zod_mini.z.looseObject({ prefix: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string()).check(zod_mini.z.minLength(1))])).check(zod_mini.z.minLength(1))) }),
|
|
8355
|
+
tests: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
8356
|
+
match: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8357
|
+
noMatch: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8358
|
+
})),
|
|
8359
|
+
reason: zod_mini.z.optional(zod_mini.z.string()),
|
|
8360
|
+
enabled: zod_mini.z.optional(zod_mini.z.boolean())
|
|
8361
|
+
});
|
|
8362
|
+
const FactorydroidPermissionRulesSchema = zod_mini.z.looseObject({
|
|
8363
|
+
version: zod_mini.z.number(),
|
|
8364
|
+
rules: zod_mini.z.array(FactorydroidPermissionRuleSchema)
|
|
8365
|
+
});
|
|
8366
|
+
/**
|
|
8290
8367
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
8291
8368
|
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
8292
8369
|
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
@@ -8294,7 +8371,8 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8294
8371
|
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
8295
8372
|
* `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
|
|
8296
8373
|
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
8297
|
-
* `subagentAutonomyLevel`, `interactionMode`)
|
|
8374
|
+
* `subagentAutonomyLevel`, `interactionMode`), and the native versioned
|
|
8375
|
+
* `permissionRules` (allow/ask/block). Fields placed here are merged
|
|
8298
8376
|
* into `settings.json` and
|
|
8299
8377
|
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
8300
8378
|
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
@@ -8305,7 +8383,8 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8305
8383
|
const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8306
8384
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8307
8385
|
commandBlocklist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8308
|
-
disabledSkills: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8386
|
+
disabledSkills: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8387
|
+
permissionRules: zod_mini.z.optional(FactorydroidPermissionRulesSchema)
|
|
8309
8388
|
});
|
|
8310
8389
|
/**
|
|
8311
8390
|
* Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
|
|
@@ -9110,6 +9189,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
9110
9189
|
codewhale: zod_mini.z.optional(CodewhalePermissionsOverrideSchema),
|
|
9111
9190
|
zed: zod_mini.z.optional(ZedPermissionsOverrideSchema),
|
|
9112
9191
|
devin: zod_mini.z.optional(DevinPermissionsOverrideSchema),
|
|
9192
|
+
qoder: zod_mini.z.optional(QoderPermissionsOverrideSchema),
|
|
9113
9193
|
"antigravity-ide": zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9114
9194
|
continue: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9115
9195
|
copilot: zod_mini.z.optional(CopilotPermissionsOverrideSchema),
|
|
@@ -9121,7 +9201,6 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
9121
9201
|
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9122
9202
|
omp: zod_mini.z.optional(OmpPermissionsOverrideSchema),
|
|
9123
9203
|
pool: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9124
|
-
qoder: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9125
9204
|
grokcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9126
9205
|
"kimi-code": zod_mini.z.optional(KimiCodePermissionsOverrideSchema),
|
|
9127
9206
|
roo: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -9642,7 +9721,9 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
9642
9721
|
qoder: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9643
9722
|
trigger: zod_mini.z.optional(zod_mini.z.string()),
|
|
9644
9723
|
glob: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
9645
|
-
description: zod_mini.z.optional(zod_mini.z.string())
|
|
9724
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
9725
|
+
channel: zod_mini.z.optional(zod_mini.z.enum(["output-style"])),
|
|
9726
|
+
name: zod_mini.z.optional(zod_mini.z.string().check(zod_mini.z.trim(), zod_mini.z.minLength(1)))
|
|
9646
9727
|
})),
|
|
9647
9728
|
augmentcode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9648
9729
|
type: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -13011,7 +13092,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13011
13092
|
},
|
|
13012
13093
|
permissions: {
|
|
13013
13094
|
kind: "replace-owned-keys",
|
|
13014
|
-
ownedKeys: ["permissions"]
|
|
13095
|
+
ownedKeys: ["permissions", "outputStyle"]
|
|
13015
13096
|
}
|
|
13016
13097
|
}
|
|
13017
13098
|
},
|
|
@@ -22425,6 +22506,7 @@ const QODER_DIR = ".qoder";
|
|
|
22425
22506
|
const QODER_RULE_FILE_NAME = "AGENTS.md";
|
|
22426
22507
|
const QODER_LOCAL_RULE_FILE_NAME = "AGENTS.local.md";
|
|
22427
22508
|
const QODER_RULES_DIR_PATH = (0, node_path.join)(QODER_DIR, "rules");
|
|
22509
|
+
const QODER_OUTPUT_STYLES_DIR_PATH = (0, node_path.join)(QODER_DIR, "output-styles");
|
|
22428
22510
|
const QODER_SETTINGS_FILE_NAME = "settings.json";
|
|
22429
22511
|
const QODER_PROJECT_MCP_FILE_NAME = ".mcp.json";
|
|
22430
22512
|
const QODER_COMMANDS_DIR_PATH = (0, node_path.join)(QODER_DIR, "commands");
|
|
@@ -29799,6 +29881,11 @@ const CORTEXCODE_SPEC = {
|
|
|
29799
29881
|
canonical: "enabled",
|
|
29800
29882
|
tool: "enabled"
|
|
29801
29883
|
}],
|
|
29884
|
+
stringPassthroughFields: [{
|
|
29885
|
+
canonical: "statusMessage",
|
|
29886
|
+
tool: "statusMessage",
|
|
29887
|
+
commandOnly: true
|
|
29888
|
+
}],
|
|
29802
29889
|
objectPassthroughFields: [{
|
|
29803
29890
|
canonical: "source",
|
|
29804
29891
|
tool: "source",
|
|
@@ -29813,7 +29900,7 @@ const CORTEXCODE_SPEC = {
|
|
|
29813
29900
|
* (project scope) and of the dedicated `~/.snowflake/cortex/hooks.json` (user
|
|
29814
29901
|
* scope), both in the Claude-Code shape: `{ "<Event>": [{ "matcher"?:
|
|
29815
29902
|
* "<regex>", "hooks": [{ "type": "command" | "prompt", ..., "timeout"?:
|
|
29816
|
-
* <seconds>, "enabled"?: <boolean>, "source"?: { "source", "ref"? } }] }] }`.
|
|
29903
|
+
* <seconds>, "enabled"?: <boolean>, "statusMessage"?: <string> (command only), "source"?: { "source", "ref"? } }] }] }`.
|
|
29817
29904
|
* The project file also holds settings rulesync does not own, so generation
|
|
29818
29905
|
* merges the `hooks` key into
|
|
29819
29906
|
* either file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it, and
|
|
@@ -30752,7 +30839,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
30752
30839
|
"allowManagedHooksOnly",
|
|
30753
30840
|
"disabledSkills",
|
|
30754
30841
|
"modelPolicy",
|
|
30755
|
-
"missionPolicy"
|
|
30842
|
+
"missionPolicy",
|
|
30843
|
+
"permissionRules"
|
|
30756
30844
|
];
|
|
30757
30845
|
//#endregion
|
|
30758
30846
|
//#region src/utils/factorydroid-settings.ts
|
|
@@ -30967,7 +31055,7 @@ const GITLABDUO_CONVERTER_CONFIG = {
|
|
|
30967
31055
|
*
|
|
30968
31056
|
* Hooks live in a dedicated file, `<project>/.gitlab/duo/hooks.json` (project,
|
|
30969
31057
|
* opt-in via `--enable-project-hooks` / `GITLAB_ENABLE_PROJECT_HOOKS=true`) or
|
|
30970
|
-
* `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...] } }`.
|
|
31058
|
+
* `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...], "PreToolUse": [...] } }`.
|
|
30971
31059
|
* rulesync owns the whole file, so it is regenerated and deleted as a unit.
|
|
30972
31060
|
*/
|
|
30973
31061
|
var GitlabduoHooks = class GitlabduoHooks extends ToolHooks {
|
|
@@ -36621,6 +36709,7 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
|
|
|
36621
36709
|
const CODEBUFF_IGNORE_FILE_NAME = ".codebuffignore";
|
|
36622
36710
|
const CODEBUFF_AGENTS_DIR = ".agents";
|
|
36623
36711
|
const CODEBUFF_MCP_FILE_NAME = "mcp.json";
|
|
36712
|
+
const CODEBUFF_GLOBAL_RULE_FILE_NAME = ".AGENTS.md";
|
|
36624
36713
|
//#endregion
|
|
36625
36714
|
//#region src/features/ignore/codebuff-ignore.ts
|
|
36626
36715
|
/**
|
|
@@ -43522,11 +43611,45 @@ const CANONICAL_TO_GITLABDUO_TRANSPORT = {
|
|
|
43522
43611
|
sse: "sse"
|
|
43523
43612
|
};
|
|
43524
43613
|
/**
|
|
43614
|
+
* Map the canonical `timeout` (milliseconds) onto GitLab Duo's `timeoutMs`, a
|
|
43615
|
+
* per-server tool-call inactivity timeout (gitlab-lsp v9.25.0+) that must be a
|
|
43616
|
+
* positive integer. An explicit `timeoutMs` on the same server wins; a
|
|
43617
|
+
* `timeout` Duo would reject is dropped with a warning.
|
|
43618
|
+
*
|
|
43619
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/e8c4a76a66e8f37d4c97fc03ffe983bfadf43f76
|
|
43620
|
+
*/
|
|
43621
|
+
function toGitlabduoTimeout({ serverName, server, logger }) {
|
|
43622
|
+
const { timeout, ...rest } = server;
|
|
43623
|
+
if (timeout === void 0 || rest.timeoutMs !== void 0) return rest;
|
|
43624
|
+
if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout <= 0) {
|
|
43625
|
+
logger?.warn(`GitLab Duo CLI MCP server "${serverName}": dropping timeout ${JSON.stringify(timeout)}, as timeoutMs must be a positive integer of milliseconds.`);
|
|
43626
|
+
return rest;
|
|
43627
|
+
}
|
|
43628
|
+
return {
|
|
43629
|
+
...rest,
|
|
43630
|
+
timeoutMs: timeout
|
|
43631
|
+
};
|
|
43632
|
+
}
|
|
43633
|
+
/**
|
|
43634
|
+
* Read GitLab Duo's `timeoutMs` back into the canonical `timeout`
|
|
43635
|
+
* (milliseconds). A server that already carries a `timeout` is left as is, so
|
|
43636
|
+
* both keys round-trip and `timeoutMs` keeps winning on the next generate.
|
|
43637
|
+
*/
|
|
43638
|
+
function fromGitlabduoTimeout(server) {
|
|
43639
|
+
if (typeof server !== "object" || server === null || Array.isArray(server)) return server;
|
|
43640
|
+
const { timeoutMs, ...rest } = server;
|
|
43641
|
+
if (timeoutMs === void 0 || rest.timeout !== void 0) return server;
|
|
43642
|
+
return {
|
|
43643
|
+
...rest,
|
|
43644
|
+
timeout: timeoutMs
|
|
43645
|
+
};
|
|
43646
|
+
}
|
|
43647
|
+
/**
|
|
43525
43648
|
* Convert one canonical server into GitLab Duo's shape. Every documented
|
|
43526
43649
|
* example spells the transport out as `type` (`stdio`, `http` or `sse`), so it
|
|
43527
43650
|
* is always written, inferred from `command` / `url` when the source leaves it
|
|
43528
|
-
* implicit.
|
|
43529
|
-
* through as authored.
|
|
43651
|
+
* implicit. The canonical `timeout` becomes `timeoutMs`; every other field —
|
|
43652
|
+
* `args`, `env`, `approvedTools`, ... — is carried through as authored.
|
|
43530
43653
|
*/
|
|
43531
43654
|
function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
43532
43655
|
if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
|
|
@@ -43535,7 +43658,7 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
43535
43658
|
reason: "no command or url",
|
|
43536
43659
|
logger
|
|
43537
43660
|
});
|
|
43538
|
-
const { type, transport, httpUrl: _httpUrl, url: _url, ...
|
|
43661
|
+
const { type, transport, httpUrl: _httpUrl, url: _url, ...others } = serverConfig;
|
|
43539
43662
|
const declared = type ?? transport;
|
|
43540
43663
|
const mapped = declared ? CANONICAL_TO_GITLABDUO_TRANSPORT[declared] : isRemoteMcpServer(serverConfig) ? "http" : "stdio";
|
|
43541
43664
|
if (!mapped) return warnAndSkipMcpServer({
|
|
@@ -43544,8 +43667,13 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
43544
43667
|
reason: `the unsupported transport "${declared}" (GitLab Duo CLI accepts stdio, http and sse)`,
|
|
43545
43668
|
logger
|
|
43546
43669
|
});
|
|
43670
|
+
const rest = toGitlabduoTimeout({
|
|
43671
|
+
serverName,
|
|
43672
|
+
server: others,
|
|
43673
|
+
logger
|
|
43674
|
+
});
|
|
43547
43675
|
if (mapped === "stdio") {
|
|
43548
|
-
const { command, args } =
|
|
43676
|
+
const { command, args } = serverConfig;
|
|
43549
43677
|
if (Array.isArray(command)) {
|
|
43550
43678
|
const [executable, ...leadingArgs] = command;
|
|
43551
43679
|
return {
|
|
@@ -43634,7 +43762,13 @@ var GitlabduoMcp = class GitlabduoMcp extends ToolMcp {
|
|
|
43634
43762
|
});
|
|
43635
43763
|
}
|
|
43636
43764
|
toRulesyncMcp() {
|
|
43637
|
-
|
|
43765
|
+
const { mcpServers, ...others } = this.json;
|
|
43766
|
+
if (typeof mcpServers !== "object" || mcpServers === null || Array.isArray(mcpServers)) return this.toRulesyncMcpDefault();
|
|
43767
|
+
const servers = Object.fromEntries(Object.entries(mcpServers).map(([name, server]) => [name, fromGitlabduoTimeout(server)]));
|
|
43768
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
43769
|
+
...others,
|
|
43770
|
+
mcpServers: servers
|
|
43771
|
+
}) });
|
|
43638
43772
|
}
|
|
43639
43773
|
validate() {
|
|
43640
43774
|
return {
|
|
@@ -46741,6 +46875,15 @@ const CONSUMED_CANONICAL_KEYS = /* @__PURE__ */ new Set([
|
|
|
46741
46875
|
]);
|
|
46742
46876
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
46743
46877
|
/**
|
|
46878
|
+
* The only keys a project override entry for a user-level server carries:
|
|
46879
|
+
* Pi reads a `.pi/mcp.json` entry without `command`, `url` or `type` as an
|
|
46880
|
+
* override of `enabled`, `exposure` and `toolExposure` of the user-level server
|
|
46881
|
+
* with the same name, and keeps the rest of that server.
|
|
46882
|
+
*
|
|
46883
|
+
* @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/docs/mcp.md
|
|
46884
|
+
*/
|
|
46885
|
+
const PI_OVERRIDE_PASSTHROUGH_KEYS = ["exposure", "toolExposure"];
|
|
46886
|
+
/**
|
|
46744
46887
|
* Pi rejects a server whose name has any other character, and treats names
|
|
46745
46888
|
* that differ only in `-` and `_` as one server (its tools share the
|
|
46746
46889
|
* `mcp__<name>` namespace with `-` replaced by `_`), rejecting the second.
|
|
@@ -46776,17 +46919,12 @@ function hasUnexpandedEnvVarRef(serverConfig) {
|
|
|
46776
46919
|
/**
|
|
46777
46920
|
* The transport fields Pi reads for one server: `type` `stdio` with a string
|
|
46778
46921
|
* `command` plus `args`, or `type` `http` with a `url`. A server Pi cannot
|
|
46779
|
-
* start or reach —
|
|
46780
|
-
*
|
|
46781
|
-
*
|
|
46922
|
+
* start or reach — a remote transport without a URL, an SSE or WebSocket
|
|
46923
|
+
* server, or a stdio entry without a command — is rejected by Pi, so it is
|
|
46924
|
+
* skipped with a warning (`null`). A server with no transport at all is a
|
|
46925
|
+
* project override instead (see `convertOverrideToPi`).
|
|
46782
46926
|
*/
|
|
46783
46927
|
function convertTransportToPi({ serverName, serverConfig, logger }) {
|
|
46784
|
-
if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
|
|
46785
|
-
toolName: "Pi",
|
|
46786
|
-
serverName,
|
|
46787
|
-
reason: "no transport",
|
|
46788
|
-
logger
|
|
46789
|
-
});
|
|
46790
46928
|
if (isRemoteMcpServer(serverConfig)) {
|
|
46791
46929
|
const url = resolveRemoteMcpUrl(serverConfig);
|
|
46792
46930
|
const stated = serverConfig.type ?? serverConfig.transport;
|
|
@@ -46827,56 +46965,117 @@ function convertTransportToPi({ serverName, serverConfig, logger }) {
|
|
|
46827
46965
|
};
|
|
46828
46966
|
}
|
|
46829
46967
|
/**
|
|
46830
|
-
*
|
|
46831
|
-
*
|
|
46832
|
-
*
|
|
46833
|
-
*
|
|
46834
|
-
*
|
|
46968
|
+
* A transport-less server in the project file becomes a Pi override entry for
|
|
46969
|
+
* the user-level server of the same name. `disabled` maps to `enabled` both
|
|
46970
|
+
* ways (an explicit `disabled: false` is "Enable in this project"), and any key
|
|
46971
|
+
* Pi ignores on an override is dropped with a warning. The global file has no
|
|
46972
|
+
* layer below it to override, so the entry is skipped there.
|
|
46973
|
+
*/
|
|
46974
|
+
function convertOverrideToPi({ serverName, serverConfig, global, logger }) {
|
|
46975
|
+
if (global) return warnAndSkipMcpServer({
|
|
46976
|
+
toolName: "Pi",
|
|
46977
|
+
serverName,
|
|
46978
|
+
reason: "no transport",
|
|
46979
|
+
logger
|
|
46980
|
+
});
|
|
46981
|
+
const record = serverConfig;
|
|
46982
|
+
const override = {};
|
|
46983
|
+
if (typeof serverConfig.disabled === "boolean") override.enabled = !serverConfig.disabled;
|
|
46984
|
+
for (const key of PI_OVERRIDE_PASSTHROUGH_KEYS) if (record[key] !== void 0) override[key] = record[key];
|
|
46985
|
+
if (Object.keys(override).length === 0) return warnAndSkipMcpServer({
|
|
46986
|
+
toolName: "Pi",
|
|
46987
|
+
serverName,
|
|
46988
|
+
reason: "no transport and nothing a Pi project override sets (disabled, exposure, toolExposure)",
|
|
46989
|
+
logger
|
|
46990
|
+
});
|
|
46991
|
+
const droppedKeys = Object.keys(record).filter((key) => key !== "disabled" && !PI_OVERRIDE_PASSTHROUGH_KEYS.includes(key) && !PROTOTYPE_POLLUTION_KEYS.has(key));
|
|
46992
|
+
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.`);
|
|
46993
|
+
return override;
|
|
46994
|
+
}
|
|
46995
|
+
/**
|
|
46996
|
+
* Whether Pi rejects the server name, either outright or because an earlier
|
|
46997
|
+
* server already took its `mcp__<name>` namespace; warns when it does.
|
|
46998
|
+
*/
|
|
46999
|
+
function isRejectedServerName({ serverName, namespaces, logger }) {
|
|
47000
|
+
if (!PI_SERVER_NAME_PATTERN.test(serverName)) {
|
|
47001
|
+
warnAndSkipMcpServer({
|
|
47002
|
+
toolName: "Pi",
|
|
47003
|
+
serverName,
|
|
47004
|
+
reason: "a name Pi rejects (use letters, digits, \"_\" and \"-\")",
|
|
47005
|
+
logger
|
|
47006
|
+
});
|
|
47007
|
+
return true;
|
|
47008
|
+
}
|
|
47009
|
+
const clash = namespaces.get(toPiNamespace(serverName));
|
|
47010
|
+
if (clash !== void 0) {
|
|
47011
|
+
warnAndSkipMcpServer({
|
|
47012
|
+
toolName: "Pi",
|
|
47013
|
+
serverName,
|
|
47014
|
+
reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
|
|
47015
|
+
logger
|
|
47016
|
+
});
|
|
47017
|
+
return true;
|
|
47018
|
+
}
|
|
47019
|
+
return false;
|
|
47020
|
+
}
|
|
47021
|
+
/**
|
|
47022
|
+
* Convert one canonical server to its Pi entry, or `null` when it is skipped.
|
|
47023
|
+
* Besides the transport fields, Pi switches a server off with `enabled: false`
|
|
47024
|
+
* and reads `timeout` in seconds (canonical: milliseconds). `auth` is dropped
|
|
47025
|
+
* with a warning from the project file, since Pi accepts it only in the global
|
|
47026
|
+
* one (it drops the whole server otherwise). A server without a transport is a
|
|
47027
|
+
* project override entry instead.
|
|
47028
|
+
*/
|
|
47029
|
+
function convertServerToPi({ serverName, serverConfig, global, logger }) {
|
|
47030
|
+
if (declaresNoTransport(serverConfig)) return convertOverrideToPi({
|
|
47031
|
+
serverName,
|
|
47032
|
+
serverConfig,
|
|
47033
|
+
global,
|
|
47034
|
+
logger
|
|
47035
|
+
});
|
|
47036
|
+
const converted = convertTransportToPi({
|
|
47037
|
+
serverName,
|
|
47038
|
+
serverConfig,
|
|
47039
|
+
logger
|
|
47040
|
+
});
|
|
47041
|
+
if (!converted) return null;
|
|
47042
|
+
for (const [key, value] of Object.entries(serverConfig)) {
|
|
47043
|
+
if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
47044
|
+
converted[key] = value;
|
|
47045
|
+
}
|
|
47046
|
+
if (serverConfig.timeout !== void 0) {
|
|
47047
|
+
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
|
|
47048
|
+
else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
|
|
47049
|
+
}
|
|
47050
|
+
if (serverConfig.disabled === true) converted.enabled = false;
|
|
47051
|
+
if (!global && "auth" in converted) {
|
|
47052
|
+
delete converted.auth;
|
|
47053
|
+
logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
|
|
47054
|
+
}
|
|
47055
|
+
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.`);
|
|
47056
|
+
return converted;
|
|
47057
|
+
}
|
|
47058
|
+
/**
|
|
47059
|
+
* Convert the canonical server map to Pi's `mcpServers` shape. A server name
|
|
47060
|
+
* Pi rejects is skipped with a warning.
|
|
46835
47061
|
*/
|
|
46836
47062
|
function convertToPiFormat({ mcpServers, global, logger }) {
|
|
46837
47063
|
const result = {};
|
|
46838
47064
|
const namespaces = /* @__PURE__ */ new Map();
|
|
46839
47065
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
46840
47066
|
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
46841
|
-
if (
|
|
46842
|
-
|
|
46843
|
-
|
|
46844
|
-
|
|
46845
|
-
|
|
46846
|
-
|
|
46847
|
-
});
|
|
46848
|
-
continue;
|
|
46849
|
-
}
|
|
46850
|
-
const clash = namespaces.get(toPiNamespace(serverName));
|
|
46851
|
-
if (clash !== void 0) {
|
|
46852
|
-
warnAndSkipMcpServer({
|
|
46853
|
-
toolName: "Pi",
|
|
46854
|
-
serverName,
|
|
46855
|
-
reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
|
|
46856
|
-
logger
|
|
46857
|
-
});
|
|
46858
|
-
continue;
|
|
46859
|
-
}
|
|
46860
|
-
const converted = convertTransportToPi({
|
|
47067
|
+
if (isRejectedServerName({
|
|
47068
|
+
serverName,
|
|
47069
|
+
namespaces,
|
|
47070
|
+
logger
|
|
47071
|
+
})) continue;
|
|
47072
|
+
const converted = convertServerToPi({
|
|
46861
47073
|
serverName,
|
|
46862
47074
|
serverConfig,
|
|
47075
|
+
global,
|
|
46863
47076
|
logger
|
|
46864
47077
|
});
|
|
46865
47078
|
if (!converted) continue;
|
|
46866
|
-
for (const [key, value] of Object.entries(serverConfig)) {
|
|
46867
|
-
if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
46868
|
-
converted[key] = value;
|
|
46869
|
-
}
|
|
46870
|
-
if (serverConfig.timeout !== void 0) {
|
|
46871
|
-
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
|
|
46872
|
-
else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
|
|
46873
|
-
}
|
|
46874
|
-
if (serverConfig.disabled === true) converted.enabled = false;
|
|
46875
|
-
if (!global && "auth" in converted) {
|
|
46876
|
-
delete converted.auth;
|
|
46877
|
-
logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
|
|
46878
|
-
}
|
|
46879
|
-
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.`);
|
|
46880
47079
|
namespaces.set(toPiNamespace(serverName), serverName);
|
|
46881
47080
|
result[serverName] = converted;
|
|
46882
47081
|
}
|
|
@@ -46885,16 +47084,19 @@ function convertToPiFormat({ mcpServers, global, logger }) {
|
|
|
46885
47084
|
/**
|
|
46886
47085
|
* Convert Pi's `mcpServers` back to the canonical shape: `enabled: false`
|
|
46887
47086
|
* becomes `disabled: true` and `timeout` (seconds) becomes milliseconds; every
|
|
46888
|
-
* other key is already canonical.
|
|
47087
|
+
* other key is already canonical. On a transport-less override entry an
|
|
47088
|
+
* explicit `enabled: true` is kept as `disabled: false`, since it switches on a
|
|
47089
|
+
* user-level server the user turned off.
|
|
46889
47090
|
*/
|
|
46890
47091
|
function convertFromPiFormat(mcpServers) {
|
|
46891
47092
|
const result = {};
|
|
46892
47093
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
46893
47094
|
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
46894
47095
|
const { enabled, timeout, ...rest } = serverConfig;
|
|
46895
|
-
const
|
|
47096
|
+
const keepsEnabled = enabled === true && declaresNoTransport(rest);
|
|
47097
|
+
const converted = enabled === false || keepsEnabled ? {
|
|
46896
47098
|
...rest,
|
|
46897
|
-
disabled:
|
|
47099
|
+
disabled: !enabled
|
|
46898
47100
|
} : rest;
|
|
46899
47101
|
if (typeof timeout === "number") converted.timeout = timeout * MILLISECONDS_PER_SECOND;
|
|
46900
47102
|
else if (timeout !== void 0) converted.timeout = timeout;
|
|
@@ -46970,8 +47172,11 @@ var PiMcp = class PiMcp extends ToolMcp {
|
|
|
46970
47172
|
});
|
|
46971
47173
|
}
|
|
46972
47174
|
toRulesyncMcp() {
|
|
46973
|
-
const
|
|
46974
|
-
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
47175
|
+
const { shared, toolOnly } = splitMcpServersByTransport(convertFromPiFormat(isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {}));
|
|
47176
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
47177
|
+
mcpServers: shared,
|
|
47178
|
+
...Object.keys(toolOnly).length > 0 && { pi: { mcpServers: toolOnly } }
|
|
47179
|
+
}, null, 2) });
|
|
46975
47180
|
}
|
|
46976
47181
|
validate() {
|
|
46977
47182
|
return {
|
|
@@ -64764,9 +64969,11 @@ const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
|
64764
64969
|
* the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
|
|
64765
64970
|
* permissions override.
|
|
64766
64971
|
*
|
|
64767
|
-
* Scope
|
|
64768
|
-
* a project `defaultTools` array *replaces* the global
|
|
64769
|
-
*
|
|
64972
|
+
* Scope layering is Pi's own rather than the union-merge most targets use:
|
|
64973
|
+
* a project `defaultTools` array with a plain name *replaces* the global
|
|
64974
|
+
* array, while one made only of `+name` / `-name` entries (Pi v0.99.0+) changes
|
|
64975
|
+
* the global selection instead. Entries are written verbatim, so the two
|
|
64976
|
+
* scopes are written independently and never combined by rulesync.
|
|
64770
64977
|
* Project scope writes `.pi/settings.json`; global scope writes
|
|
64771
64978
|
* `~/.pi/agent/settings.json`.
|
|
64772
64979
|
*
|
|
@@ -65426,6 +65633,13 @@ function convertPoolToRulesync({ tools, paths }) {
|
|
|
65426
65633
|
//#region src/features/permissions/qoder-permissions.ts
|
|
65427
65634
|
/** Top-level key of `.qoder/settings.json` that rulesync owns. */
|
|
65428
65635
|
const QODER_PERMISSIONS_KEY = "permissions";
|
|
65636
|
+
/**
|
|
65637
|
+
* Top-level key the `qoder` override authors: the active output style. Qoder
|
|
65638
|
+
* also reads `general.outputStyle`, but the top-level key wins, so that is the
|
|
65639
|
+
* one written and imported.
|
|
65640
|
+
* @see https://docs.qoder.com/cli/settings-reference
|
|
65641
|
+
*/
|
|
65642
|
+
const QODER_OUTPUT_STYLE_KEY = "outputStyle";
|
|
65429
65643
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
65430
65644
|
/** Prefix of the fully qualified MCP tool names (`mcp__<server>__<tool>`). */
|
|
65431
65645
|
const MCP_TOOL_PREFIX = "mcp__";
|
|
@@ -65585,9 +65799,11 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
|
|
|
65585
65799
|
* through the shared-config gateway, the sibling keys of `permissions`
|
|
65586
65800
|
* (`additionalDirectories`, `trustDirectories`) are kept, and the file is
|
|
65587
65801
|
* never deleted. `.qoder/settings.local.json` (where `/allow` and `/deny`
|
|
65588
|
-
* persist) is left to the user.
|
|
65802
|
+
* persist) is left to the user. The `qoder` override's `outputStyle` is
|
|
65803
|
+
* written to (and imported from) the top-level `outputStyle` key.
|
|
65589
65804
|
*
|
|
65590
65805
|
* @see https://docs.qoder.com/en/cli/permissions
|
|
65806
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
65591
65807
|
*/
|
|
65592
65808
|
var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
65593
65809
|
constructor(params) {
|
|
@@ -65626,11 +65842,14 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65626
65842
|
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
65627
65843
|
});
|
|
65628
65844
|
const existingPermissions = isRecord$1(existing[QODER_PERMISSIONS_KEY]) ? existing[QODER_PERMISSIONS_KEY] : {};
|
|
65845
|
+
const config = rulesyncPermissions.getJson();
|
|
65629
65846
|
const lists = buildQoderRuleLists({
|
|
65630
|
-
config
|
|
65847
|
+
config,
|
|
65631
65848
|
existingPermissions,
|
|
65632
65849
|
logger
|
|
65633
65850
|
});
|
|
65851
|
+
const outputStyle = config.qoder?.outputStyle?.trim();
|
|
65852
|
+
if (outputStyle !== void 0) logger?.warn(`Qoder permissions: qoder.outputStyle = ${JSON.stringify(outputStyle)} was written to ${(0, node_path.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.`);
|
|
65634
65853
|
const permissions = { ...existingPermissions };
|
|
65635
65854
|
for (const action of ACTIONS) if (lists[action].length > 0) permissions[action] = lists[action];
|
|
65636
65855
|
else delete permissions[action];
|
|
@@ -65642,7 +65861,10 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65642
65861
|
fileKey: sharedConfigFileKey(paths),
|
|
65643
65862
|
feature: "permissions",
|
|
65644
65863
|
existingContent,
|
|
65645
|
-
patch: {
|
|
65864
|
+
patch: {
|
|
65865
|
+
[QODER_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0,
|
|
65866
|
+
...outputStyle !== void 0 && { [QODER_OUTPUT_STYLE_KEY]: outputStyle }
|
|
65867
|
+
},
|
|
65646
65868
|
filePath
|
|
65647
65869
|
}),
|
|
65648
65870
|
validate: true,
|
|
@@ -65663,7 +65885,12 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65663
65885
|
const existing = rules[parsed.pattern];
|
|
65664
65886
|
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) rules[parsed.pattern] = action;
|
|
65665
65887
|
}
|
|
65666
|
-
|
|
65888
|
+
const outputStyle = settings[QODER_OUTPUT_STYLE_KEY];
|
|
65889
|
+
const hasOutputStyle = typeof outputStyle === "string" && outputStyle.trim() !== "";
|
|
65890
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
65891
|
+
permission,
|
|
65892
|
+
...hasOutputStyle && { qoder: { outputStyle } }
|
|
65893
|
+
}, null, 2) });
|
|
65667
65894
|
}
|
|
65668
65895
|
validate() {
|
|
65669
65896
|
return {
|
|
@@ -84711,8 +84938,8 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84711
84938
|
* Derive the subagent name from this instance's relative file path.
|
|
84712
84939
|
*
|
|
84713
84940
|
* The tool-side layout is `<name>/AGENT.md`, so the name is the parent
|
|
84714
|
-
* directory of the file.
|
|
84715
|
-
*
|
|
84941
|
+
* directory of the file. Devin also reads a flat `<name>.md`, which import
|
|
84942
|
+
* picks up too; its name is the basename without extension.
|
|
84716
84943
|
*/
|
|
84717
84944
|
getSubagentName() {
|
|
84718
84945
|
const relativeFilePath = this.getRelativeFilePath();
|
|
@@ -84805,8 +85032,9 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84805
85032
|
/**
|
|
84806
85033
|
* Subagent inside a Devin plugin bundle (`<plugin>/agents/<name>/AGENT.md`).
|
|
84807
85034
|
* Plugin subagents use the same profile format as project ones and are
|
|
84808
|
-
* exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md
|
|
84809
|
-
* the directory form is written to match the
|
|
85035
|
+
* exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md`,
|
|
85036
|
+
* which import picks up too; the directory form is written to match the
|
|
85037
|
+
* `devin` target.
|
|
84810
85038
|
*
|
|
84811
85039
|
* @see https://docs.devin.ai/cli/extensibility/plugins/overview
|
|
84812
85040
|
*/
|
|
@@ -88368,6 +88596,17 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
88368
88596
|
};
|
|
88369
88597
|
//#endregion
|
|
88370
88598
|
//#region src/features/subagents/subagents-processor.ts
|
|
88599
|
+
/**
|
|
88600
|
+
* The globs import scans for a tool: its import-only patterns when it reads
|
|
88601
|
+
* more layouts than Rulesync writes, otherwise the generation pattern.
|
|
88602
|
+
*/
|
|
88603
|
+
function importFilePatternsOf(meta) {
|
|
88604
|
+
return meta.importFilePatterns ?? meta.filePattern;
|
|
88605
|
+
}
|
|
88606
|
+
/** Whether unparseable files are left out of this import or orphan sweep. */
|
|
88607
|
+
function skipsInvalidFiles(meta, forDeletion) {
|
|
88608
|
+
return meta.skipInvalidFiles === true || !forDeletion && meta.skipInvalidImportFiles === true;
|
|
88609
|
+
}
|
|
88371
88610
|
const SubagentsProcessorToolTargetSchema = zod_mini.z.enum(subagentsProcessorToolTargetTuple);
|
|
88372
88611
|
/**
|
|
88373
88612
|
* Factory Map mapping tool targets to their subagent factories.
|
|
@@ -88564,7 +88803,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
88564
88803
|
supportsProject: true,
|
|
88565
88804
|
supportsSimulated: false,
|
|
88566
88805
|
supportsGlobal: true,
|
|
88567
|
-
filePattern: "*/AGENT.md"
|
|
88806
|
+
filePattern: "*/AGENT.md",
|
|
88807
|
+
importFilePatterns: ["*.md", "*/AGENT.md"],
|
|
88808
|
+
skipInvalidImportFiles: true
|
|
88568
88809
|
}
|
|
88569
88810
|
}],
|
|
88570
88811
|
["devin-plugin", {
|
|
@@ -88573,7 +88814,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
88573
88814
|
supportsProject: true,
|
|
88574
88815
|
supportsSimulated: false,
|
|
88575
88816
|
supportsGlobal: false,
|
|
88576
|
-
filePattern: "*/AGENT.md"
|
|
88817
|
+
filePattern: "*/AGENT.md",
|
|
88818
|
+
importFilePatterns: ["*.md", "*/AGENT.md"],
|
|
88819
|
+
skipInvalidImportFiles: true
|
|
88577
88820
|
}
|
|
88578
88821
|
}],
|
|
88579
88822
|
["factorydroid", {
|
|
@@ -89052,6 +89295,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
89052
89295
|
const factory = this.getFactory(this.toolTarget);
|
|
89053
89296
|
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
89054
89297
|
const roots = forDeletion ? [paths.relativeDirPath] : [paths.relativeDirPath, ...paths.importDirPaths ?? []];
|
|
89298
|
+
const filePatterns = forDeletion ? factory.meta.filePattern : importFilePatternsOf(factory.meta);
|
|
89055
89299
|
const toolSubagents = [];
|
|
89056
89300
|
const claimedRelativeFilePaths = /* @__PURE__ */ new Map();
|
|
89057
89301
|
const claimedImportIdentities = new ClaimedIdentities();
|
|
@@ -89068,7 +89312,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
89068
89312
|
this.logger.warn(`Skipping the orphan sweep for ${quoteForLog(baseDir)}; nothing under it is deleted: ${stripControlCharacters(formatError(error))}`);
|
|
89069
89313
|
continue;
|
|
89070
89314
|
}
|
|
89071
|
-
const subagentFilePaths = (await findFilesByGlobs(
|
|
89315
|
+
const subagentFilePaths = (await findFilesByGlobs(filePatterns, {
|
|
89072
89316
|
cwd: baseDir,
|
|
89073
89317
|
followSymbolicLinks: !forDeletion
|
|
89074
89318
|
})).toSorted((left, right) => compareRelativePathsShallowestFirst((0, node_path.relative)(baseDir, left), (0, node_path.relative)(baseDir, right)));
|
|
@@ -89082,7 +89326,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
89082
89326
|
})));
|
|
89083
89327
|
ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
|
|
89084
89328
|
}
|
|
89085
|
-
const parsedFiles = factory.meta
|
|
89329
|
+
const parsedFiles = skipsInvalidFiles(factory.meta, forDeletion) ? (await Promise.all(ownedFilePaths.map(async (path) => {
|
|
89086
89330
|
try {
|
|
89087
89331
|
return {
|
|
89088
89332
|
path,
|
|
@@ -91958,6 +92202,85 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
91958
92202
|
}
|
|
91959
92203
|
};
|
|
91960
92204
|
//#endregion
|
|
92205
|
+
//#region src/features/rules/codebuff-rule.ts
|
|
92206
|
+
/**
|
|
92207
|
+
* Rule generator for Codebuff (Freebuff), global scope only.
|
|
92208
|
+
*
|
|
92209
|
+
* Codebuff reads one user-level instructions file from the home directory:
|
|
92210
|
+
* the first hidden file matching `AGENTS.md` or `CLAUDE.md` (case-insensitive,
|
|
92211
|
+
* in that priority order), so the root rule is written to `~/.AGENTS.md`. There
|
|
92212
|
+
* is no user-level rules directory, so non-root rules fold into that file.
|
|
92213
|
+
*
|
|
92214
|
+
* Project scope is not supported here: Codebuff reads the project-root
|
|
92215
|
+
* `AGENTS.md`, which the `agentsmd` target already writes. The RulesProcessor
|
|
92216
|
+
* keeps this target out of project mode (`supportsProject: false`), so the
|
|
92217
|
+
* settable paths are the home-directory ones regardless of `global`.
|
|
92218
|
+
*
|
|
92219
|
+
* @see https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/run-state.ts
|
|
92220
|
+
* @see https://github.com/CodebuffAI/freebuff/blob/main/common/src/constants/knowledge.ts
|
|
92221
|
+
*/
|
|
92222
|
+
var CodebuffRule = class CodebuffRule extends ToolRule {
|
|
92223
|
+
static getSettablePaths(_options = {}) {
|
|
92224
|
+
return { root: {
|
|
92225
|
+
relativeDirPath: ".",
|
|
92226
|
+
relativeFilePath: CODEBUFF_GLOBAL_RULE_FILE_NAME
|
|
92227
|
+
} };
|
|
92228
|
+
}
|
|
92229
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
92230
|
+
const { root } = this.getSettablePaths({ global: true });
|
|
92231
|
+
if (relativeFilePath !== root.relativeFilePath) throw new Error(`CodebuffRule only supports the root rule: ${relativeFilePath}`);
|
|
92232
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, root.relativeDirPath, root.relativeFilePath));
|
|
92233
|
+
return new CodebuffRule({
|
|
92234
|
+
outputRoot,
|
|
92235
|
+
relativeDirPath: root.relativeDirPath,
|
|
92236
|
+
relativeFilePath: root.relativeFilePath,
|
|
92237
|
+
fileContent,
|
|
92238
|
+
validate,
|
|
92239
|
+
root: true
|
|
92240
|
+
});
|
|
92241
|
+
}
|
|
92242
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
92243
|
+
const { root } = this.getSettablePaths({ global: true });
|
|
92244
|
+
const frontmatter = rulesyncRule.getFrontmatter();
|
|
92245
|
+
return new CodebuffRule({
|
|
92246
|
+
outputRoot,
|
|
92247
|
+
relativeDirPath: root.relativeDirPath,
|
|
92248
|
+
relativeFilePath: root.relativeFilePath,
|
|
92249
|
+
fileContent: rulesyncRule.getBody(),
|
|
92250
|
+
validate,
|
|
92251
|
+
root: frontmatter.root ?? false,
|
|
92252
|
+
description: frontmatter.description,
|
|
92253
|
+
globs: frontmatter.globs
|
|
92254
|
+
});
|
|
92255
|
+
}
|
|
92256
|
+
toRulesyncRule() {
|
|
92257
|
+
return this.toRulesyncRuleDefault();
|
|
92258
|
+
}
|
|
92259
|
+
validate() {
|
|
92260
|
+
return {
|
|
92261
|
+
success: true,
|
|
92262
|
+
error: null
|
|
92263
|
+
};
|
|
92264
|
+
}
|
|
92265
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
92266
|
+
const { root } = this.getSettablePaths({ global: true });
|
|
92267
|
+
return new CodebuffRule({
|
|
92268
|
+
outputRoot,
|
|
92269
|
+
relativeDirPath,
|
|
92270
|
+
relativeFilePath,
|
|
92271
|
+
fileContent: "",
|
|
92272
|
+
validate: false,
|
|
92273
|
+
root: relativeDirPath === root.relativeDirPath && relativeFilePath === root.relativeFilePath
|
|
92274
|
+
});
|
|
92275
|
+
}
|
|
92276
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
92277
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
92278
|
+
rulesyncRule,
|
|
92279
|
+
toolTarget: "codebuff"
|
|
92280
|
+
});
|
|
92281
|
+
}
|
|
92282
|
+
};
|
|
92283
|
+
//#endregion
|
|
91961
92284
|
//#region src/features/rules/codewhale-rule.ts
|
|
91962
92285
|
/**
|
|
91963
92286
|
* Rule generator for Codewhale.
|
|
@@ -93595,7 +93918,7 @@ var DshRule = class extends NestedAgentsmdRule {
|
|
|
93595
93918
|
* `getExtraFixedFiles`): Droid loads "direct `.md` children of each
|
|
93596
93919
|
* `output-styles` directory".
|
|
93597
93920
|
*/
|
|
93598
|
-
const OUTPUT_STYLE_FILE_GLOB = "*.md";
|
|
93921
|
+
const OUTPUT_STYLE_FILE_GLOB$1 = "*.md";
|
|
93599
93922
|
/**
|
|
93600
93923
|
* Rule generator for Factory Droid.
|
|
93601
93924
|
*
|
|
@@ -93717,7 +94040,7 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
93717
94040
|
static getExtraFixedFiles({ global = false } = {}) {
|
|
93718
94041
|
return [...this.getChannelPaths({ global }).map(({ path }) => path), {
|
|
93719
94042
|
relativeDirPath: this.getOutputStylesDirPath(),
|
|
93720
|
-
relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
|
|
94043
|
+
relativeFilePath: OUTPUT_STYLE_FILE_GLOB$1,
|
|
93721
94044
|
importOnly: true
|
|
93722
94045
|
}];
|
|
93723
94046
|
}
|
|
@@ -95572,6 +95895,12 @@ const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set([
|
|
|
95572
95895
|
* Any other mode is kept in the `qoder` section so it survives a round trip.
|
|
95573
95896
|
*/
|
|
95574
95897
|
const INFERABLE_TRIGGERS = /* @__PURE__ */ new Set(["always_on", "glob"]);
|
|
95898
|
+
/**
|
|
95899
|
+
* The file-name glob `getExtraFixedFiles` returns for the output-styles
|
|
95900
|
+
* directory, so import enumerates every style in it (never deletion: the
|
|
95901
|
+
* directory also holds hand-written styles).
|
|
95902
|
+
*/
|
|
95903
|
+
const OUTPUT_STYLE_FILE_GLOB = "*.md";
|
|
95575
95904
|
function normalizeGlobList(value) {
|
|
95576
95905
|
if (value === void 0) return [];
|
|
95577
95906
|
return (typeof value === "string" ? splitBraceAwareList(value) : value).map((glob) => glob.trim()).filter((glob) => glob !== "");
|
|
@@ -95605,22 +95934,59 @@ function resolveTrigger(frontmatter) {
|
|
|
95605
95934
|
* A `qoder.trigger` (for example `model_decision` or `manual`) always takes
|
|
95606
95935
|
* precedence and round-trips through import.
|
|
95607
95936
|
*
|
|
95937
|
+
* A non-root rule with `qoder.channel: output-style` becomes a custom output
|
|
95938
|
+
* style instead, `.qoder/output-styles/<rule file name>.md` (both scopes),
|
|
95939
|
+
* with the rule's `description` and optional `qoder.name` as the style's
|
|
95940
|
+
* `description` / `name` frontmatter.
|
|
95941
|
+
*
|
|
95608
95942
|
* @see https://docs.qoder.com/en/cli/04-extending-qoder-cli/memory
|
|
95943
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
95609
95944
|
*/
|
|
95610
95945
|
var QoderRule = class QoderRule extends ToolRule {
|
|
95611
95946
|
frontmatter;
|
|
95612
95947
|
body;
|
|
95613
|
-
|
|
95948
|
+
outputStyle;
|
|
95949
|
+
constructor({ frontmatter, body, outputStyle = false, ...rest }) {
|
|
95614
95950
|
if (rest.validate) {
|
|
95615
95951
|
const result = QoderRuleFrontmatterSchema.safeParse(frontmatter);
|
|
95616
95952
|
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
95617
95953
|
}
|
|
95618
95954
|
super({
|
|
95619
95955
|
...rest,
|
|
95620
|
-
fileContent: rest.root ? body : stringifyFrontmatter(body, frontmatter)
|
|
95956
|
+
fileContent: rest.root || outputStyle && Object.keys(frontmatter).length === 0 ? body : stringifyFrontmatter(body, frontmatter)
|
|
95621
95957
|
});
|
|
95622
95958
|
this.frontmatter = frontmatter;
|
|
95623
95959
|
this.body = body;
|
|
95960
|
+
this.outputStyle = outputStyle;
|
|
95961
|
+
}
|
|
95962
|
+
/**
|
|
95963
|
+
* Whether the given path is a custom output style: a direct `.md` child of
|
|
95964
|
+
* `.qoder/output-styles/`, at either scope.
|
|
95965
|
+
*/
|
|
95966
|
+
static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
|
|
95967
|
+
return relativeDirPath === QODER_OUTPUT_STYLES_DIR_PATH && (0, node_path.extname)(relativeFilePath) === ".md" && (0, node_path.basename)(relativeFilePath) === relativeFilePath;
|
|
95968
|
+
}
|
|
95969
|
+
/** Whether a rule opts into the `output-style` channel (non-root only). */
|
|
95970
|
+
static isOutputStyleRule(rulesyncRule) {
|
|
95971
|
+
const frontmatter = rulesyncRule.getFrontmatter();
|
|
95972
|
+
return !frontmatter.root && frontmatter.qoder?.channel === "output-style";
|
|
95973
|
+
}
|
|
95974
|
+
/**
|
|
95975
|
+
* The output-styles directory, enumerated for import only: it also holds
|
|
95976
|
+
* styles users write by hand (including personal ones in
|
|
95977
|
+
* `~/.qoder/output-styles/`) that rulesync cannot tell from its own, so
|
|
95978
|
+
* `--delete` leaves it alone.
|
|
95979
|
+
*/
|
|
95980
|
+
static getExtraFixedFiles(_params = {}) {
|
|
95981
|
+
return [{
|
|
95982
|
+
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
95983
|
+
relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
|
|
95984
|
+
importOnly: true
|
|
95985
|
+
}];
|
|
95986
|
+
}
|
|
95987
|
+
/** Qoder loads output styles itself; they are not rules to reference. */
|
|
95988
|
+
isExcludedFromRootReferences() {
|
|
95989
|
+
return this.outputStyle;
|
|
95624
95990
|
}
|
|
95625
95991
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
95626
95992
|
return {
|
|
@@ -95633,6 +95999,23 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95633
95999
|
}
|
|
95634
96000
|
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
95635
96001
|
const paths = this.getSettablePaths({ global });
|
|
96002
|
+
if (this.isOutputStylePath({
|
|
96003
|
+
relativeDirPath,
|
|
96004
|
+
relativeFilePath
|
|
96005
|
+
})) {
|
|
96006
|
+
const filePath = (0, node_path.join)(outputRoot, QODER_OUTPUT_STYLES_DIR_PATH, relativeFilePath);
|
|
96007
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
96008
|
+
return new QoderRule({
|
|
96009
|
+
outputRoot,
|
|
96010
|
+
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
96011
|
+
relativeFilePath,
|
|
96012
|
+
frontmatter,
|
|
96013
|
+
body: body.trim(),
|
|
96014
|
+
validate,
|
|
96015
|
+
root: false,
|
|
96016
|
+
outputStyle: true
|
|
96017
|
+
});
|
|
96018
|
+
}
|
|
95636
96019
|
if (relativeDirPath !== void 0 ? relativeDirPath !== QODER_RULES_DIR_PATH : relativeFilePath === paths.root.relativeFilePath) {
|
|
95637
96020
|
const rootDirPath = relativeDirPath ?? paths.root.relativeDirPath;
|
|
95638
96021
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootDirPath, paths.root.relativeFilePath));
|
|
@@ -95665,6 +96048,11 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95665
96048
|
const root = rulesyncFrontmatter.root ?? false;
|
|
95666
96049
|
const paths = this.getSettablePaths({ global });
|
|
95667
96050
|
const body = rulesyncRule.getBody();
|
|
96051
|
+
if (this.isOutputStyleRule(rulesyncRule)) return this.buildOutputStyle({
|
|
96052
|
+
outputRoot,
|
|
96053
|
+
rulesyncRule,
|
|
96054
|
+
validate
|
|
96055
|
+
});
|
|
95668
96056
|
if (root) return new QoderRule({
|
|
95669
96057
|
outputRoot,
|
|
95670
96058
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -95684,8 +96072,32 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95684
96072
|
root: false
|
|
95685
96073
|
});
|
|
95686
96074
|
}
|
|
96075
|
+
/**
|
|
96076
|
+
* Write an `output-style` rule as `.qoder/output-styles/<name>.md`. The file
|
|
96077
|
+
* name is the rule's own, so a rule nested under `.rulesync/rules/` is
|
|
96078
|
+
* refused: Qoder reads only the files directly in `output-styles/`.
|
|
96079
|
+
*/
|
|
96080
|
+
static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
|
|
96081
|
+
const { description, qoder } = rulesyncRule.getFrontmatter();
|
|
96082
|
+
const sourcePath = rulesyncRule.getRelativeFilePath();
|
|
96083
|
+
if ((0, node_path.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}.`);
|
|
96084
|
+
const name = qoder?.name;
|
|
96085
|
+
return new QoderRule({
|
|
96086
|
+
outputRoot,
|
|
96087
|
+
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
96088
|
+
relativeFilePath: `${(0, node_path.basename)(sourcePath, (0, node_path.extname)(sourcePath))}.md`,
|
|
96089
|
+
frontmatter: {
|
|
96090
|
+
...name !== void 0 && { name },
|
|
96091
|
+
...description !== void 0 && { description }
|
|
96092
|
+
},
|
|
96093
|
+
body: rulesyncRule.getBody(),
|
|
96094
|
+
validate,
|
|
96095
|
+
root: false,
|
|
96096
|
+
outputStyle: true
|
|
96097
|
+
});
|
|
96098
|
+
}
|
|
95687
96099
|
static buildFrontmatter(rulesyncFrontmatter, relativeFilePath) {
|
|
95688
|
-
const { trigger: storedTrigger, glob: storedGlob, ...section } = rulesyncFrontmatter.qoder ?? {};
|
|
96100
|
+
const { trigger: storedTrigger, glob: storedGlob, channel: _channel, name: _name, ...section } = rulesyncFrontmatter.qoder ?? {};
|
|
95689
96101
|
const specificGlobs = normalizeGlobList(storedGlob ?? rulesyncFrontmatter.globs).filter((glob) => !UNIVERSAL_GLOBS$1.has(glob));
|
|
95690
96102
|
let trigger = storedTrigger ?? (specificGlobs.length > 0 ? "glob" : "always_on");
|
|
95691
96103
|
const description = section.description ?? rulesyncFrontmatter.description;
|
|
@@ -95703,6 +96115,25 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95703
96115
|
}
|
|
95704
96116
|
toRulesyncRule() {
|
|
95705
96117
|
if (this.isRoot()) return this.toRulesyncRuleDefault();
|
|
96118
|
+
if (this.outputStyle) {
|
|
96119
|
+
const { name, description } = this.frontmatter;
|
|
96120
|
+
return new RulesyncRule({
|
|
96121
|
+
outputRoot: this.getOutputRoot(),
|
|
96122
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
96123
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
96124
|
+
frontmatter: {
|
|
96125
|
+
root: false,
|
|
96126
|
+
targets: ["qoder"],
|
|
96127
|
+
...typeof description === "string" && { description },
|
|
96128
|
+
qoder: {
|
|
96129
|
+
channel: "output-style",
|
|
96130
|
+
...typeof name === "string" && name.trim() !== "" && { name }
|
|
96131
|
+
}
|
|
96132
|
+
},
|
|
96133
|
+
body: this.body,
|
|
96134
|
+
validate: true
|
|
96135
|
+
});
|
|
96136
|
+
}
|
|
95706
96137
|
const trigger = resolveTrigger(this.frontmatter);
|
|
95707
96138
|
const { trigger: _trigger, alwaysApply: _alwaysApply, description, glob, paths, ...unmapped } = this.frontmatter;
|
|
95708
96139
|
const fileGlobs = normalizeGlobList(glob ?? paths);
|
|
@@ -95751,6 +96182,10 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95751
96182
|
return this.body;
|
|
95752
96183
|
}
|
|
95753
96184
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
96185
|
+
const outputStyle = this.isOutputStylePath({
|
|
96186
|
+
relativeDirPath,
|
|
96187
|
+
relativeFilePath
|
|
96188
|
+
});
|
|
95754
96189
|
return new QoderRule({
|
|
95755
96190
|
outputRoot,
|
|
95756
96191
|
relativeDirPath,
|
|
@@ -95758,7 +96193,8 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95758
96193
|
frontmatter: {},
|
|
95759
96194
|
body: "",
|
|
95760
96195
|
validate: false,
|
|
95761
|
-
root: relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md"
|
|
96196
|
+
root: !outputStyle && relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md",
|
|
96197
|
+
outputStyle
|
|
95762
96198
|
});
|
|
95763
96199
|
}
|
|
95764
96200
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -97445,6 +97881,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
97445
97881
|
localRootFileName: CODEBUDDY_LOCAL_RULE_FILE_NAME
|
|
97446
97882
|
}
|
|
97447
97883
|
}],
|
|
97884
|
+
["codebuff", {
|
|
97885
|
+
class: CodebuffRule,
|
|
97886
|
+
meta: {
|
|
97887
|
+
extension: "md",
|
|
97888
|
+
supportsProject: false,
|
|
97889
|
+
supportsGlobal: true,
|
|
97890
|
+
ruleDiscoveryMode: "auto",
|
|
97891
|
+
collisionPolicy: "fold"
|
|
97892
|
+
}
|
|
97893
|
+
}],
|
|
97448
97894
|
["codewhale", {
|
|
97449
97895
|
class: CodewhaleRule,
|
|
97450
97896
|
meta: {
|
|
@@ -97875,7 +98321,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
97875
98321
|
}]
|
|
97876
98322
|
]);
|
|
97877
98323
|
const allToolTargetKeys = [...toolRuleFactories.keys()];
|
|
97878
|
-
const rulesProcessorToolTargets = allToolTargetKeys
|
|
98324
|
+
const rulesProcessorToolTargets = allToolTargetKeys.filter((target) => {
|
|
98325
|
+
return toolRuleFactories.get(target)?.meta.supportsProject !== false;
|
|
98326
|
+
});
|
|
97879
98327
|
const rulesProcessorToolTargetsGlobal = allToolTargetKeys.filter((target) => {
|
|
97880
98328
|
return toolRuleFactories.get(target)?.meta.supportsGlobal ?? false;
|
|
97881
98329
|
});
|
|
@@ -97954,7 +98402,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
97954
98402
|
const factory = this.getFactory(this.toolTarget);
|
|
97955
98403
|
const { meta } = factory;
|
|
97956
98404
|
this.warnForIncludeRootOption(meta);
|
|
97957
|
-
this.
|
|
98405
|
+
this.warnForOutputStyleRuleOptions({
|
|
97958
98406
|
rules: alignedRules,
|
|
97959
98407
|
factory
|
|
97960
98408
|
});
|
|
@@ -98188,22 +98636,25 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
98188
98636
|
}) !== "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.");
|
|
98189
98637
|
}
|
|
98190
98638
|
/**
|
|
98191
|
-
* Warn about `factorydroid` frontmatter that does not do what it
|
|
98192
|
-
* it does: an `output-style` rule that also targets other tools
|
|
98193
|
-
* as an ordinary rule (so presentation instructions end up in
|
|
98194
|
-
* rules), and
|
|
98195
|
-
* Checked while generating for
|
|
98639
|
+
* Warn about `factorydroid` / `qoder` frontmatter that does not do what it
|
|
98640
|
+
* looks like it does: an `output-style` rule that also targets other tools
|
|
98641
|
+
* reaches them as an ordinary rule (so presentation instructions end up in
|
|
98642
|
+
* their project rules), and `<tool>.name` is read only with
|
|
98643
|
+
* `channel: output-style`. Checked while generating for that tool only, so
|
|
98644
|
+
* each warning prints once.
|
|
98196
98645
|
*/
|
|
98197
|
-
|
|
98198
|
-
|
|
98646
|
+
warnForOutputStyleRuleOptions({ rules, factory }) {
|
|
98647
|
+
const toolTarget = this.toolTarget;
|
|
98648
|
+
if (toolTarget !== "factorydroid" && toolTarget !== "qoder") return;
|
|
98199
98649
|
for (const rule of rules) {
|
|
98200
98650
|
const frontmatter = rule.getFrontmatter();
|
|
98201
|
-
|
|
98202
|
-
|
|
98651
|
+
const section = frontmatter[toolTarget];
|
|
98652
|
+
if (frontmatter.root || !section || !factory.class.isTargetedByRulesyncRule(rule)) continue;
|
|
98653
|
+
const { channel, name } = section;
|
|
98203
98654
|
const fileName = stripControlCharacters(rule.getRelativeFilePath());
|
|
98204
98655
|
const targets = frontmatter.targets ?? ["*"];
|
|
98205
|
-
if (channel === "output-style" && targets.some((target) => target !==
|
|
98206
|
-
if (channel !== "output-style" && name !== void 0) this.logger.warn(`${fileName} sets
|
|
98656
|
+
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"}.`);
|
|
98657
|
+
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.`);
|
|
98207
98658
|
}
|
|
98208
98659
|
}
|
|
98209
98660
|
/**
|
|
@@ -102650,6 +103101,12 @@ Object.defineProperty(exports, "hasEnclosingMarkOutsideKeycap", {
|
|
|
102650
103101
|
return hasEnclosingMarkOutsideKeycap;
|
|
102651
103102
|
}
|
|
102652
103103
|
});
|
|
103104
|
+
Object.defineProperty(exports, "hasExecutableBit", {
|
|
103105
|
+
enumerable: true,
|
|
103106
|
+
get: function() {
|
|
103107
|
+
return hasExecutableBit;
|
|
103108
|
+
}
|
|
103109
|
+
});
|
|
102653
103110
|
Object.defineProperty(exports, "importFromTool", {
|
|
102654
103111
|
enumerable: true,
|
|
102655
103112
|
get: function() {
|
|
@@ -102806,6 +103263,12 @@ Object.defineProperty(exports, "resolveRulesyncSourceWritePath", {
|
|
|
102806
103263
|
return resolveRulesyncSourceWritePath;
|
|
102807
103264
|
}
|
|
102808
103265
|
});
|
|
103266
|
+
Object.defineProperty(exports, "resolvedPathEscapesRoot", {
|
|
103267
|
+
enumerable: true,
|
|
103268
|
+
get: function() {
|
|
103269
|
+
return resolvedPathEscapesRoot;
|
|
103270
|
+
}
|
|
103271
|
+
});
|
|
102809
103272
|
Object.defineProperty(exports, "runWithDirectoryRollback", {
|
|
102810
103273
|
enumerable: true,
|
|
102811
103274
|
get: function() {
|