rulesync 32.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 +429 -127
- package/dist/cli/index.js +429 -127
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-Bmb7ofrF.cjs → import-Bx0zNGBx.cjs} +804 -145
- package/dist/{import-DgKWnDni.js → import-DFwX4TTE.js} +777 -130
- package/dist/{import-DgKWnDni.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
|
},
|
|
@@ -21766,22 +21847,29 @@ function mergeOpencodeV2Entries({ config, legacyKey, pluralKey, lower }) {
|
|
|
21766
21847
|
const entry = asOpencodeEntries(value);
|
|
21767
21848
|
const lowered = entry === null ? null : lower(entry);
|
|
21768
21849
|
if (Object.hasOwn(merged, name) || lowered === null) continue;
|
|
21769
|
-
|
|
21770
|
-
value: lowered,
|
|
21771
|
-
enumerable: true,
|
|
21772
|
-
configurable: true,
|
|
21773
|
-
writable: true
|
|
21774
|
-
});
|
|
21850
|
+
setOwnEntry(merged, name, lowered);
|
|
21775
21851
|
}
|
|
21776
21852
|
return Object.keys(merged).length > 0 ? merged : legacy;
|
|
21777
21853
|
}
|
|
21778
21854
|
/**
|
|
21855
|
+
* Sets `target[key]` as an own, enumerable property. A plain assignment to a
|
|
21856
|
+
* key named `__proto__` would set the prototype instead.
|
|
21857
|
+
*/
|
|
21858
|
+
function setOwnEntry(target, key, value) {
|
|
21859
|
+
Object.defineProperty(target, key, {
|
|
21860
|
+
value,
|
|
21861
|
+
enumerable: true,
|
|
21862
|
+
configurable: true,
|
|
21863
|
+
writable: true
|
|
21864
|
+
});
|
|
21865
|
+
}
|
|
21866
|
+
/**
|
|
21779
21867
|
* Returns the inline agent entries of an OpenCode config in the V1 `agent`
|
|
21780
21868
|
* shape. Since v1.18.24 OpenCode V1 also reads the V2 `agents` spelling and
|
|
21781
21869
|
* lowers it, so a layout that does so (`readsV2ConfigSpellings`) gets those
|
|
21782
21870
|
* entries too.
|
|
21783
21871
|
*
|
|
21784
|
-
* @see https://github.com/anomalyco/opencode/blob/v1.18.
|
|
21872
|
+
* @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
|
|
21785
21873
|
*/
|
|
21786
21874
|
function getOpencodeAgentEntries({ config, layout = OPENCODE_LAYOUT }) {
|
|
21787
21875
|
if (!layout.readsV2ConfigSpellings) return asOpencodeEntries(config.agent);
|
|
@@ -21800,7 +21888,7 @@ function getOpencodeAgentEntries({ config, layout = OPENCODE_LAYOUT }) {
|
|
|
21800
21888
|
* Other fields are kept: V1 drops a V2 `subagent`, but rulesync reads it as
|
|
21801
21889
|
* `subtask` everywhere (as V2 does), so it is left for the command importer.
|
|
21802
21890
|
*
|
|
21803
|
-
* @see https://github.com/anomalyco/opencode/blob/v1.18.
|
|
21891
|
+
* @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
|
|
21804
21892
|
*/
|
|
21805
21893
|
function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
|
|
21806
21894
|
if (!layout.readsV2ConfigSpellings) return asOpencodeEntries(config.command);
|
|
@@ -21823,7 +21911,7 @@ function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
|
|
|
21823
21911
|
* reads V2 spellings, a flat V2 `skills` array is accepted too: its entries
|
|
21824
21912
|
* that are not `http(s)://` URLs are the paths, as OpenCode V1 lowers them.
|
|
21825
21913
|
*
|
|
21826
|
-
* @see https://github.com/anomalyco/opencode/blob/v1.18.
|
|
21914
|
+
* @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
|
|
21827
21915
|
*/
|
|
21828
21916
|
function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
|
|
21829
21917
|
if (Array.isArray(config.skills)) {
|
|
@@ -21833,6 +21921,160 @@ function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
|
|
|
21833
21921
|
const skills = asOpencodeEntries(config.skills);
|
|
21834
21922
|
return skills !== null && Array.isArray(skills.paths) ? skills.paths : [];
|
|
21835
21923
|
}
|
|
21924
|
+
/**
|
|
21925
|
+
* Whether a record is itself an MCP server entry rather than a V2 container
|
|
21926
|
+
* (`mcp.servers`, `mcp.timeout`): a server — or a server literally named
|
|
21927
|
+
* `type` / `enabled` — carries a scalar `type` or `enabled`. Mirrors OpenCode
|
|
21928
|
+
* V1's `isDirectServer`.
|
|
21929
|
+
*/
|
|
21930
|
+
function isDirectOpencodeMcpServer(value) {
|
|
21931
|
+
return ["type", "enabled"].some((key) => Object.hasOwn(value, key) && (value[key] === null || typeof value[key] !== "object" || Array.isArray(value[key])));
|
|
21932
|
+
}
|
|
21933
|
+
const OPENCODE_V2_TIMEOUT_KEYS = [
|
|
21934
|
+
"startup",
|
|
21935
|
+
"catalog",
|
|
21936
|
+
"execution"
|
|
21937
|
+
];
|
|
21938
|
+
/**
|
|
21939
|
+
* Whether an MCP server entry uses a V2-only per-server spelling, so that
|
|
21940
|
+
* OpenCode V1 lowers it: `disabled`, `codemode`, an object `timeout`, or a
|
|
21941
|
+
* snake_case `oauth` key.
|
|
21942
|
+
*/
|
|
21943
|
+
function isOpencodeV2NativeServer(value) {
|
|
21944
|
+
const oauth = value.oauth;
|
|
21945
|
+
return Object.hasOwn(value, "disabled") || Object.hasOwn(value, "codemode") || isRecord$1(value.timeout) || isRecord$1(oauth) && [
|
|
21946
|
+
"client_id",
|
|
21947
|
+
"client_secret",
|
|
21948
|
+
"callback_port",
|
|
21949
|
+
"redirect_uri"
|
|
21950
|
+
].some((key) => Object.hasOwn(oauth, key));
|
|
21951
|
+
}
|
|
21952
|
+
/**
|
|
21953
|
+
* Lowers a V2 timeout object (`{ startup, catalog, execution }`) to V1's single
|
|
21954
|
+
* number, the way OpenCode V1 does: only a `catalog` equal to `execution`, with
|
|
21955
|
+
* no `startup`, has a V1 equivalent. Anything else — including a value that is
|
|
21956
|
+
* not a positive integer — is `undefined` (dropped).
|
|
21957
|
+
*/
|
|
21958
|
+
function lowerOpencodeV2Timeout(value) {
|
|
21959
|
+
if (!hasPositiveIntegerTimeouts(value) || value.startup !== void 0 || typeof value.catalog !== "number") return;
|
|
21960
|
+
return value.catalog === value.execution ? value.catalog : void 0;
|
|
21961
|
+
}
|
|
21962
|
+
const OPENCODE_V2_OAUTH_KEYS = /* @__PURE__ */ new Map([
|
|
21963
|
+
["client_id", "clientId"],
|
|
21964
|
+
["client_secret", "clientSecret"],
|
|
21965
|
+
["callback_port", "callbackPort"],
|
|
21966
|
+
["redirect_uri", "redirectUri"]
|
|
21967
|
+
]);
|
|
21968
|
+
/**
|
|
21969
|
+
* V2 `oauth` keys that have no V1 spelling. OpenCode V1 drops them when it
|
|
21970
|
+
* lowers a server, so they are dropped (and reported) here too rather than
|
|
21971
|
+
* being written back as a snake_case key among camelCase ones.
|
|
21972
|
+
*/
|
|
21973
|
+
const OPENCODE_V2_UNSUPPORTED_OAUTH_KEYS = /* @__PURE__ */ new Set(["auth_server_metadata_url"]);
|
|
21974
|
+
function lowerOpencodeV2OAuth(oauth, path, onDropped) {
|
|
21975
|
+
const lowered = {};
|
|
21976
|
+
for (const [key, value] of Object.entries(oauth)) {
|
|
21977
|
+
if (OPENCODE_V2_UNSUPPORTED_OAUTH_KEYS.has(key)) {
|
|
21978
|
+
onDropped?.(`${path}.${key}`, "unsupported");
|
|
21979
|
+
continue;
|
|
21980
|
+
}
|
|
21981
|
+
setOwnEntry(lowered, OPENCODE_V2_OAUTH_KEYS.get(key) ?? key, value);
|
|
21982
|
+
}
|
|
21983
|
+
return lowered;
|
|
21984
|
+
}
|
|
21985
|
+
/**
|
|
21986
|
+
* Lowers one V2-spelled MCP server entry to V1's shape, as OpenCode V1 does:
|
|
21987
|
+
* `disabled` becomes `enabled` (an explicit `enabled` wins), an object
|
|
21988
|
+
* `timeout` becomes a number or is dropped, and snake_case `oauth` keys become
|
|
21989
|
+
* camelCase. `codemode` is kept: Rulesync passes it through where V1 drops it.
|
|
21990
|
+
*
|
|
21991
|
+
* Like V1, only an entry that is a local or remote server is lowered, and one
|
|
21992
|
+
* whose `enabled` or `disabled` is not a boolean is left as it is. Unlike V1,
|
|
21993
|
+
* such an entry is not dropped but passed on, so the schema rejects it loudly.
|
|
21994
|
+
*/
|
|
21995
|
+
function lowerOpencodeV2Server(value, path, onDropped) {
|
|
21996
|
+
if (!isRecord$1(value) || value.type !== "local" && value.type !== "remote" || Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean" || Object.hasOwn(value, "disabled") && typeof value.disabled !== "boolean") return value;
|
|
21997
|
+
const { disabled, timeout, ...rest } = value;
|
|
21998
|
+
const lowered = { ...rest };
|
|
21999
|
+
if (!Object.hasOwn(value, "enabled")) lowered.enabled = disabled !== true;
|
|
22000
|
+
else if (value.enabled === disabled) onDropped?.(`${path}.disabled`, "conflict");
|
|
22001
|
+
const lowTimeout = isRecord$1(timeout) ? lowerOpencodeV2Timeout(timeout) : timeout;
|
|
22002
|
+
if (lowTimeout !== void 0) lowered.timeout = lowTimeout;
|
|
22003
|
+
else if (isRecord$1(timeout) && Object.keys(timeout).length > 0) onDropped?.(`${path}.timeout`, "unsupported");
|
|
22004
|
+
if (isRecord$1(value.oauth)) lowered.oauth = lowerOpencodeV2OAuth(value.oauth, `${path}.oauth`, onDropped);
|
|
22005
|
+
return lowered;
|
|
22006
|
+
}
|
|
22007
|
+
function isPositiveInteger(value) {
|
|
22008
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
22009
|
+
}
|
|
22010
|
+
/** Whether each V2 timeout value a record carries is a positive integer. */
|
|
22011
|
+
function hasPositiveIntegerTimeouts(value) {
|
|
22012
|
+
return OPENCODE_V2_TIMEOUT_KEYS.every((key) => value[key] === void 0 || isPositiveInteger(value[key]));
|
|
22013
|
+
}
|
|
22014
|
+
/**
|
|
22015
|
+
* Whether `mcp.timeout` is V2's global timeout object rather than a server
|
|
22016
|
+
* named `timeout`. As in OpenCode V1, each of its values must be a positive
|
|
22017
|
+
* integer; anything else is read as a server, which the schema then rejects.
|
|
22018
|
+
*/
|
|
22019
|
+
function isOpencodeV2GlobalTimeout(value) {
|
|
22020
|
+
return isRecord$1(value) && !isDirectOpencodeMcpServer(value) && (Object.keys(value).length === 0 || OPENCODE_V2_TIMEOUT_KEYS.some((key) => Object.hasOwn(value, key))) && hasPositiveIntegerTimeouts(value);
|
|
22021
|
+
}
|
|
22022
|
+
function getOpencodeV2McpEnvelope(mcp) {
|
|
22023
|
+
const nested = mcp.servers;
|
|
22024
|
+
return isRecord$1(nested) && !isDirectOpencodeMcpServer(nested) ? nested : null;
|
|
22025
|
+
}
|
|
22026
|
+
/**
|
|
22027
|
+
* Returns the paths of the V2-only containers in an OpenCode `mcp` value that
|
|
22028
|
+
* a write in V1's flat shape does not keep: a non-empty `mcp.servers`
|
|
22029
|
+
* envelope, and a non-empty global `mcp.timeout`.
|
|
22030
|
+
*/
|
|
22031
|
+
function findOpencodeV2McpContainers(mcp) {
|
|
22032
|
+
if (!isRecord$1(mcp)) return [];
|
|
22033
|
+
const envelope = getOpencodeV2McpEnvelope(mcp);
|
|
22034
|
+
return [...envelope !== null && Object.keys(envelope).length > 0 ? ["mcp.servers"] : [], ...isOpencodeV2GlobalTimeout(mcp.timeout) && Object.keys(mcp.timeout).length > 0 ? ["mcp.timeout"] : []];
|
|
22035
|
+
}
|
|
22036
|
+
/**
|
|
22037
|
+
* Lowers OpenCode V2 `mcp` spellings found in a V1 config to the flat V1 map,
|
|
22038
|
+
* the way OpenCode V1 itself does since v1.18.24 (`normalizeMcp`):
|
|
22039
|
+
*
|
|
22040
|
+
* - entries of the `mcp.servers` envelope are merged into the map, a flat entry
|
|
22041
|
+
* of the same name winning;
|
|
22042
|
+
* - V2-spelled server entries (`disabled`, an object `timeout`, snake_case
|
|
22043
|
+
* `oauth` keys) are lowered by {@link lowerOpencodeV2Server};
|
|
22044
|
+
* - the global `mcp.timeout` object is skipped. V1 moves it to
|
|
22045
|
+
* `experimental.mcp_timeout`, a default request timeout that has no
|
|
22046
|
+
* canonical counterpart, so it is not read as a server either.
|
|
22047
|
+
*
|
|
22048
|
+
* Unlike V1, an envelope entry that is not a valid server is not dropped: it
|
|
22049
|
+
* is passed on, so the schema rejects it loudly rather than it vanishing
|
|
22050
|
+
* without a word. Each value that is dropped — a non-empty global timeout, a
|
|
22051
|
+
* per-server timeout object or `oauth` key with no V1 equivalent, or an
|
|
22052
|
+
* envelope entry shadowed by a different flat entry — is reported through
|
|
22053
|
+
* `onDropped`.
|
|
22054
|
+
*
|
|
22055
|
+
* @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
|
|
22056
|
+
* @see https://opencode.ai/v2/docs/mcp-servers/
|
|
22057
|
+
*/
|
|
22058
|
+
function lowerOpencodeV2Mcp(mcp, onDropped) {
|
|
22059
|
+
if (!isRecord$1(mcp)) return mcp;
|
|
22060
|
+
const envelope = getOpencodeV2McpEnvelope(mcp);
|
|
22061
|
+
const globalTimeout = isOpencodeV2GlobalTimeout(mcp.timeout);
|
|
22062
|
+
if (findOpencodeV2McpContainers(mcp).includes("mcp.timeout")) onDropped?.("mcp.timeout", "unsupported");
|
|
22063
|
+
const servers = {};
|
|
22064
|
+
for (const [name, value] of Object.entries(mcp)) {
|
|
22065
|
+
if (name === "servers" && envelope !== null || name === "timeout" && globalTimeout) continue;
|
|
22066
|
+
setOwnEntry(servers, name, isRecord$1(value) && isOpencodeV2NativeServer(value) ? lowerOpencodeV2Server(value, `mcp.${name}`, onDropped) : value);
|
|
22067
|
+
}
|
|
22068
|
+
for (const [name, value] of Object.entries(envelope ?? {})) {
|
|
22069
|
+
const path = `mcp.servers.${name}`;
|
|
22070
|
+
if (Object.hasOwn(servers, name)) {
|
|
22071
|
+
if (!(0, node_util.isDeepStrictEqual)(servers[name], value)) onDropped?.(path, "conflict");
|
|
22072
|
+
continue;
|
|
22073
|
+
}
|
|
22074
|
+
setOwnEntry(servers, name, lowerOpencodeV2Server(value, path, onDropped));
|
|
22075
|
+
}
|
|
22076
|
+
return servers;
|
|
22077
|
+
}
|
|
21836
22078
|
/** Matches a string whose entire value is an OpenCode `{file:...}` reference. */
|
|
21837
22079
|
const OPENCODE_FILE_TEMPLATE_PATTERN = /^\s*\{file:(.+?)\}\s*$/;
|
|
21838
22080
|
/**
|
|
@@ -22264,6 +22506,7 @@ const QODER_DIR = ".qoder";
|
|
|
22264
22506
|
const QODER_RULE_FILE_NAME = "AGENTS.md";
|
|
22265
22507
|
const QODER_LOCAL_RULE_FILE_NAME = "AGENTS.local.md";
|
|
22266
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");
|
|
22267
22510
|
const QODER_SETTINGS_FILE_NAME = "settings.json";
|
|
22268
22511
|
const QODER_PROJECT_MCP_FILE_NAME = ".mcp.json";
|
|
22269
22512
|
const QODER_COMMANDS_DIR_PATH = (0, node_path.join)(QODER_DIR, "commands");
|
|
@@ -29638,6 +29881,11 @@ const CORTEXCODE_SPEC = {
|
|
|
29638
29881
|
canonical: "enabled",
|
|
29639
29882
|
tool: "enabled"
|
|
29640
29883
|
}],
|
|
29884
|
+
stringPassthroughFields: [{
|
|
29885
|
+
canonical: "statusMessage",
|
|
29886
|
+
tool: "statusMessage",
|
|
29887
|
+
commandOnly: true
|
|
29888
|
+
}],
|
|
29641
29889
|
objectPassthroughFields: [{
|
|
29642
29890
|
canonical: "source",
|
|
29643
29891
|
tool: "source",
|
|
@@ -29652,7 +29900,7 @@ const CORTEXCODE_SPEC = {
|
|
|
29652
29900
|
* (project scope) and of the dedicated `~/.snowflake/cortex/hooks.json` (user
|
|
29653
29901
|
* scope), both in the Claude-Code shape: `{ "<Event>": [{ "matcher"?:
|
|
29654
29902
|
* "<regex>", "hooks": [{ "type": "command" | "prompt", ..., "timeout"?:
|
|
29655
|
-
* <seconds>, "enabled"?: <boolean>, "source"?: { "source", "ref"? } }] }] }`.
|
|
29903
|
+
* <seconds>, "enabled"?: <boolean>, "statusMessage"?: <string> (command only), "source"?: { "source", "ref"? } }] }] }`.
|
|
29656
29904
|
* The project file also holds settings rulesync does not own, so generation
|
|
29657
29905
|
* merges the `hooks` key into
|
|
29658
29906
|
* either file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it, and
|
|
@@ -30591,7 +30839,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
30591
30839
|
"allowManagedHooksOnly",
|
|
30592
30840
|
"disabledSkills",
|
|
30593
30841
|
"modelPolicy",
|
|
30594
|
-
"missionPolicy"
|
|
30842
|
+
"missionPolicy",
|
|
30843
|
+
"permissionRules"
|
|
30595
30844
|
];
|
|
30596
30845
|
//#endregion
|
|
30597
30846
|
//#region src/utils/factorydroid-settings.ts
|
|
@@ -30806,7 +31055,7 @@ const GITLABDUO_CONVERTER_CONFIG = {
|
|
|
30806
31055
|
*
|
|
30807
31056
|
* Hooks live in a dedicated file, `<project>/.gitlab/duo/hooks.json` (project,
|
|
30808
31057
|
* opt-in via `--enable-project-hooks` / `GITLAB_ENABLE_PROJECT_HOOKS=true`) or
|
|
30809
|
-
* `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...] } }`.
|
|
31058
|
+
* `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...], "PreToolUse": [...] } }`.
|
|
30810
31059
|
* rulesync owns the whole file, so it is regenerated and deleted as a unit.
|
|
30811
31060
|
*/
|
|
30812
31061
|
var GitlabduoHooks = class GitlabduoHooks extends ToolHooks {
|
|
@@ -36460,6 +36709,7 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
|
|
|
36460
36709
|
const CODEBUFF_IGNORE_FILE_NAME = ".codebuffignore";
|
|
36461
36710
|
const CODEBUFF_AGENTS_DIR = ".agents";
|
|
36462
36711
|
const CODEBUFF_MCP_FILE_NAME = "mcp.json";
|
|
36712
|
+
const CODEBUFF_GLOBAL_RULE_FILE_NAME = ".AGENTS.md";
|
|
36463
36713
|
//#endregion
|
|
36464
36714
|
//#region src/features/ignore/codebuff-ignore.ts
|
|
36465
36715
|
/**
|
|
@@ -43361,11 +43611,45 @@ const CANONICAL_TO_GITLABDUO_TRANSPORT = {
|
|
|
43361
43611
|
sse: "sse"
|
|
43362
43612
|
};
|
|
43363
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
|
+
/**
|
|
43364
43648
|
* Convert one canonical server into GitLab Duo's shape. Every documented
|
|
43365
43649
|
* example spells the transport out as `type` (`stdio`, `http` or `sse`), so it
|
|
43366
43650
|
* is always written, inferred from `command` / `url` when the source leaves it
|
|
43367
|
-
* implicit.
|
|
43368
|
-
* through as authored.
|
|
43651
|
+
* implicit. The canonical `timeout` becomes `timeoutMs`; every other field —
|
|
43652
|
+
* `args`, `env`, `approvedTools`, ... — is carried through as authored.
|
|
43369
43653
|
*/
|
|
43370
43654
|
function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
43371
43655
|
if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
|
|
@@ -43374,7 +43658,7 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
43374
43658
|
reason: "no command or url",
|
|
43375
43659
|
logger
|
|
43376
43660
|
});
|
|
43377
|
-
const { type, transport, httpUrl: _httpUrl, url: _url, ...
|
|
43661
|
+
const { type, transport, httpUrl: _httpUrl, url: _url, ...others } = serverConfig;
|
|
43378
43662
|
const declared = type ?? transport;
|
|
43379
43663
|
const mapped = declared ? CANONICAL_TO_GITLABDUO_TRANSPORT[declared] : isRemoteMcpServer(serverConfig) ? "http" : "stdio";
|
|
43380
43664
|
if (!mapped) return warnAndSkipMcpServer({
|
|
@@ -43383,8 +43667,13 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
|
|
|
43383
43667
|
reason: `the unsupported transport "${declared}" (GitLab Duo CLI accepts stdio, http and sse)`,
|
|
43384
43668
|
logger
|
|
43385
43669
|
});
|
|
43670
|
+
const rest = toGitlabduoTimeout({
|
|
43671
|
+
serverName,
|
|
43672
|
+
server: others,
|
|
43673
|
+
logger
|
|
43674
|
+
});
|
|
43386
43675
|
if (mapped === "stdio") {
|
|
43387
|
-
const { command, args } =
|
|
43676
|
+
const { command, args } = serverConfig;
|
|
43388
43677
|
if (Array.isArray(command)) {
|
|
43389
43678
|
const [executable, ...leadingArgs] = command;
|
|
43390
43679
|
return {
|
|
@@ -43473,7 +43762,13 @@ var GitlabduoMcp = class GitlabduoMcp extends ToolMcp {
|
|
|
43473
43762
|
});
|
|
43474
43763
|
}
|
|
43475
43764
|
toRulesyncMcp() {
|
|
43476
|
-
|
|
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
|
+
}) });
|
|
43477
43772
|
}
|
|
43478
43773
|
validate() {
|
|
43479
43774
|
return {
|
|
@@ -45581,18 +45876,21 @@ var KiroMcp = class KiroMcp extends ToolMcp {
|
|
|
45581
45876
|
};
|
|
45582
45877
|
//#endregion
|
|
45583
45878
|
//#region src/features/mcp/opencode-mcp.ts
|
|
45879
|
+
const OpencodeMcpV2DisabledSchema = zod_mini.z.optional(zod_mini.z.boolean());
|
|
45584
45880
|
const OpencodeMcpLocalServerSchema = zod_mini.z.looseObject({
|
|
45585
45881
|
type: zod_mini.z.literal("local"),
|
|
45586
45882
|
command: zod_mini.z.array(zod_mini.z.string()),
|
|
45587
45883
|
environment: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
45588
45884
|
enabled: zod_mini.z._default(zod_mini.z.boolean(), true),
|
|
45885
|
+
disabled: OpencodeMcpV2DisabledSchema,
|
|
45589
45886
|
cwd: zod_mini.z.optional(zod_mini.z.string())
|
|
45590
45887
|
});
|
|
45591
45888
|
const OpencodeMcpRemoteServerSchema = zod_mini.z.looseObject({
|
|
45592
45889
|
type: zod_mini.z.literal("remote"),
|
|
45593
45890
|
url: zod_mini.z.string(),
|
|
45594
45891
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
45595
|
-
enabled: zod_mini.z._default(zod_mini.z.boolean(), true)
|
|
45892
|
+
enabled: zod_mini.z._default(zod_mini.z.boolean(), true),
|
|
45893
|
+
disabled: OpencodeMcpV2DisabledSchema
|
|
45596
45894
|
});
|
|
45597
45895
|
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = [
|
|
45598
45896
|
"timeout",
|
|
@@ -45604,7 +45902,7 @@ const OPENCODE_MCP_TRANSPORT_KEYS = [
|
|
|
45604
45902
|
...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
|
|
45605
45903
|
...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
|
|
45606
45904
|
...OPENCODE_PASSTHROUGH_SERVER_FIELDS
|
|
45607
|
-
].filter((key) => key !== "enabled");
|
|
45905
|
+
].filter((key) => key !== "enabled" && key !== "disabled");
|
|
45608
45906
|
/**
|
|
45609
45907
|
* A bare toggle entry: `{"enabled": <bool>}` with no transport of its own,
|
|
45610
45908
|
* disabling a server another config layer defines. It is the third member of
|
|
@@ -45732,7 +46030,7 @@ const OPENCODE_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
|
45732
46030
|
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
45733
46031
|
const dropped = Object.keys(serverConfig).filter((key) => !OPENCODE_TOGGLE_KEPT_KEYS.has(key));
|
|
45734
46032
|
if (dropped.length === 0) return;
|
|
45735
|
-
logger?.warn(`OpenCode MCP:
|
|
46033
|
+
logger?.warn(`OpenCode MCP: ${quoteValueForWarning(serverName)} declares no transport, so it is written as a toggle entry and ${dropped.toSorted().map(quoteValueForWarning).join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
|
|
45736
46034
|
}
|
|
45737
46035
|
/**
|
|
45738
46036
|
* Convert standard MCP format to OpenCode native format
|
|
@@ -45812,9 +46110,35 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
45812
46110
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
45813
46111
|
static layout = OPENCODE_LAYOUT;
|
|
45814
46112
|
json;
|
|
45815
|
-
|
|
46113
|
+
/**
|
|
46114
|
+
* With a `logger`, the V2 values that lowering the content drops are warned
|
|
46115
|
+
* about (see {@link OpencodeMcp.lowerConfig}); `fromFile` passes one so the
|
|
46116
|
+
* warnings come from the lowering that builds the parsed view rather than
|
|
46117
|
+
* from an extra pass of their own. (`validate()`, which the parent
|
|
46118
|
+
* constructor calls before this view exists, lowers separately and silently.)
|
|
46119
|
+
*/
|
|
46120
|
+
constructor({ logger, ...params }) {
|
|
45816
46121
|
super(params);
|
|
45817
|
-
this.json = OpencodeConfigSchema.parse((0, jsonc_parser.parse)(this.fileContent || "{}"));
|
|
46122
|
+
this.json = OpencodeConfigSchema.parse(this.constructor.lowerConfig((0, jsonc_parser.parse)(this.fileContent || "{}"), logger));
|
|
46123
|
+
}
|
|
46124
|
+
/**
|
|
46125
|
+
* Lowers the OpenCode V2 `mcp` spellings of a parsed config for a layout
|
|
46126
|
+
* that reads them (see {@link lowerOpencodeV2Mcp}), so every reader — import,
|
|
46127
|
+
* the toggle lookup on generate, and the rules feature's `instructions`
|
|
46128
|
+
* write to the same file — accepts a V2-shaped `mcp`. Only the parsed view
|
|
46129
|
+
* is lowered; content written back keeps whatever `mcp` a feature that does
|
|
46130
|
+
* not own the key found there. With a `logger`, each V2 value the lowering
|
|
46131
|
+
* drops is warned about.
|
|
46132
|
+
*/
|
|
46133
|
+
static lowerConfig(json, logger) {
|
|
46134
|
+
if (!this.layout.readsV2ConfigSpellings || !isRecord$1(json) || json.mcp === void 0) return json;
|
|
46135
|
+
return {
|
|
46136
|
+
...json,
|
|
46137
|
+
mcp: lowerOpencodeV2Mcp(json.mcp, (path, reason) => {
|
|
46138
|
+
const quoted = quoteValueForWarning(path);
|
|
46139
|
+
logger?.warn(reason === "conflict" ? `OpenCode MCP: ${quoted} conflicts with a V1 value that takes precedence (the flat "mcp" entry of the same name, or "enabled"), so it is dropped on import.` : `OpenCode MCP: ${quoted} has no Rulesync equivalent, so it is dropped on import.`);
|
|
46140
|
+
})
|
|
46141
|
+
};
|
|
45818
46142
|
}
|
|
45819
46143
|
getJson() {
|
|
45820
46144
|
return this.json;
|
|
@@ -45831,7 +46155,7 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
45831
46155
|
relativeFilePath: this.layout.jsonFileName
|
|
45832
46156
|
};
|
|
45833
46157
|
}
|
|
45834
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
46158
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false, logger }) {
|
|
45835
46159
|
const basePaths = this.getSettablePaths({ global });
|
|
45836
46160
|
const jsonDir = (0, node_path.join)(outputRoot, basePaths.relativeDirPath);
|
|
45837
46161
|
let fileContent = null;
|
|
@@ -45853,7 +46177,8 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
45853
46177
|
relativeDirPath: basePaths.relativeDirPath,
|
|
45854
46178
|
relativeFilePath,
|
|
45855
46179
|
fileContent: JSON.stringify(newJson, null, 2),
|
|
45856
|
-
validate
|
|
46180
|
+
validate,
|
|
46181
|
+
logger
|
|
45857
46182
|
});
|
|
45858
46183
|
}
|
|
45859
46184
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
@@ -45872,7 +46197,12 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
45872
46197
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
45873
46198
|
replacement: "{env:$1}"
|
|
45874
46199
|
});
|
|
45875
|
-
const
|
|
46200
|
+
const existingJson = (0, jsonc_parser.parse)(fileContent || "{}");
|
|
46201
|
+
const existingMcp = OpencodeConfigSchema.safeParse(this.lowerConfig(existingJson));
|
|
46202
|
+
if (this.layout.readsV2ConfigSpellings && isRecord$1(existingJson)) {
|
|
46203
|
+
const containers = findOpencodeV2McpContainers(existingJson.mcp);
|
|
46204
|
+
if (containers.length > 0) logger?.warn(`OpenCode MCP: the whole "mcp" key is rewritten in the flat V1 shape, so ${containers.map(quoteValueForWarning).join(" and ")} ${containers.length === 1 ? "is" : "are"} not kept.`);
|
|
46205
|
+
}
|
|
45876
46206
|
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
|
|
45877
46207
|
return new this({
|
|
45878
46208
|
outputRoot,
|
|
@@ -45972,7 +46302,7 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
45972
46302
|
}
|
|
45973
46303
|
validate() {
|
|
45974
46304
|
const json = parseJsonc$6(this.fileContent || "{}");
|
|
45975
|
-
const result = OpencodeConfigSchema.safeParse(json);
|
|
46305
|
+
const result = OpencodeConfigSchema.safeParse(this.constructor.lowerConfig(json));
|
|
45976
46306
|
if (!result.success) return {
|
|
45977
46307
|
success: false,
|
|
45978
46308
|
error: result.error
|
|
@@ -46545,6 +46875,15 @@ const CONSUMED_CANONICAL_KEYS = /* @__PURE__ */ new Set([
|
|
|
46545
46875
|
]);
|
|
46546
46876
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
46547
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
|
+
/**
|
|
46548
46887
|
* Pi rejects a server whose name has any other character, and treats names
|
|
46549
46888
|
* that differ only in `-` and `_` as one server (its tools share the
|
|
46550
46889
|
* `mcp__<name>` namespace with `-` replaced by `_`), rejecting the second.
|
|
@@ -46580,17 +46919,12 @@ function hasUnexpandedEnvVarRef(serverConfig) {
|
|
|
46580
46919
|
/**
|
|
46581
46920
|
* The transport fields Pi reads for one server: `type` `stdio` with a string
|
|
46582
46921
|
* `command` plus `args`, or `type` `http` with a `url`. A server Pi cannot
|
|
46583
|
-
* start or reach —
|
|
46584
|
-
*
|
|
46585
|
-
*
|
|
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`).
|
|
46586
46926
|
*/
|
|
46587
46927
|
function convertTransportToPi({ serverName, serverConfig, logger }) {
|
|
46588
|
-
if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
|
|
46589
|
-
toolName: "Pi",
|
|
46590
|
-
serverName,
|
|
46591
|
-
reason: "no transport",
|
|
46592
|
-
logger
|
|
46593
|
-
});
|
|
46594
46928
|
if (isRemoteMcpServer(serverConfig)) {
|
|
46595
46929
|
const url = resolveRemoteMcpUrl(serverConfig);
|
|
46596
46930
|
const stated = serverConfig.type ?? serverConfig.transport;
|
|
@@ -46631,56 +46965,117 @@ function convertTransportToPi({ serverName, serverConfig, logger }) {
|
|
|
46631
46965
|
};
|
|
46632
46966
|
}
|
|
46633
46967
|
/**
|
|
46634
|
-
*
|
|
46635
|
-
*
|
|
46636
|
-
*
|
|
46637
|
-
*
|
|
46638
|
-
*
|
|
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.
|
|
46639
47061
|
*/
|
|
46640
47062
|
function convertToPiFormat({ mcpServers, global, logger }) {
|
|
46641
47063
|
const result = {};
|
|
46642
47064
|
const namespaces = /* @__PURE__ */ new Map();
|
|
46643
47065
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
46644
47066
|
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
46645
|
-
if (
|
|
46646
|
-
|
|
46647
|
-
|
|
46648
|
-
|
|
46649
|
-
|
|
46650
|
-
|
|
46651
|
-
});
|
|
46652
|
-
continue;
|
|
46653
|
-
}
|
|
46654
|
-
const clash = namespaces.get(toPiNamespace(serverName));
|
|
46655
|
-
if (clash !== void 0) {
|
|
46656
|
-
warnAndSkipMcpServer({
|
|
46657
|
-
toolName: "Pi",
|
|
46658
|
-
serverName,
|
|
46659
|
-
reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
|
|
46660
|
-
logger
|
|
46661
|
-
});
|
|
46662
|
-
continue;
|
|
46663
|
-
}
|
|
46664
|
-
const converted = convertTransportToPi({
|
|
47067
|
+
if (isRejectedServerName({
|
|
47068
|
+
serverName,
|
|
47069
|
+
namespaces,
|
|
47070
|
+
logger
|
|
47071
|
+
})) continue;
|
|
47072
|
+
const converted = convertServerToPi({
|
|
46665
47073
|
serverName,
|
|
46666
47074
|
serverConfig,
|
|
47075
|
+
global,
|
|
46667
47076
|
logger
|
|
46668
47077
|
});
|
|
46669
47078
|
if (!converted) continue;
|
|
46670
|
-
for (const [key, value] of Object.entries(serverConfig)) {
|
|
46671
|
-
if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
46672
|
-
converted[key] = value;
|
|
46673
|
-
}
|
|
46674
|
-
if (serverConfig.timeout !== void 0) {
|
|
46675
|
-
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
|
|
46676
|
-
else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
|
|
46677
|
-
}
|
|
46678
|
-
if (serverConfig.disabled === true) converted.enabled = false;
|
|
46679
|
-
if (!global && "auth" in converted) {
|
|
46680
|
-
delete converted.auth;
|
|
46681
|
-
logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
|
|
46682
|
-
}
|
|
46683
|
-
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.`);
|
|
46684
47079
|
namespaces.set(toPiNamespace(serverName), serverName);
|
|
46685
47080
|
result[serverName] = converted;
|
|
46686
47081
|
}
|
|
@@ -46689,16 +47084,19 @@ function convertToPiFormat({ mcpServers, global, logger }) {
|
|
|
46689
47084
|
/**
|
|
46690
47085
|
* Convert Pi's `mcpServers` back to the canonical shape: `enabled: false`
|
|
46691
47086
|
* becomes `disabled: true` and `timeout` (seconds) becomes milliseconds; every
|
|
46692
|
-
* 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.
|
|
46693
47090
|
*/
|
|
46694
47091
|
function convertFromPiFormat(mcpServers) {
|
|
46695
47092
|
const result = {};
|
|
46696
47093
|
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
46697
47094
|
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
46698
47095
|
const { enabled, timeout, ...rest } = serverConfig;
|
|
46699
|
-
const
|
|
47096
|
+
const keepsEnabled = enabled === true && declaresNoTransport(rest);
|
|
47097
|
+
const converted = enabled === false || keepsEnabled ? {
|
|
46700
47098
|
...rest,
|
|
46701
|
-
disabled:
|
|
47099
|
+
disabled: !enabled
|
|
46702
47100
|
} : rest;
|
|
46703
47101
|
if (typeof timeout === "number") converted.timeout = timeout * MILLISECONDS_PER_SECOND;
|
|
46704
47102
|
else if (timeout !== void 0) converted.timeout = timeout;
|
|
@@ -46774,8 +47172,11 @@ var PiMcp = class PiMcp extends ToolMcp {
|
|
|
46774
47172
|
});
|
|
46775
47173
|
}
|
|
46776
47174
|
toRulesyncMcp() {
|
|
46777
|
-
const
|
|
46778
|
-
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) });
|
|
46779
47180
|
}
|
|
46780
47181
|
validate() {
|
|
46781
47182
|
return {
|
|
@@ -64568,9 +64969,11 @@ const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
|
64568
64969
|
* the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
|
|
64569
64970
|
* permissions override.
|
|
64570
64971
|
*
|
|
64571
|
-
* Scope
|
|
64572
|
-
* a project `defaultTools` array *replaces* the global
|
|
64573
|
-
*
|
|
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.
|
|
64574
64977
|
* Project scope writes `.pi/settings.json`; global scope writes
|
|
64575
64978
|
* `~/.pi/agent/settings.json`.
|
|
64576
64979
|
*
|
|
@@ -65230,6 +65633,13 @@ function convertPoolToRulesync({ tools, paths }) {
|
|
|
65230
65633
|
//#region src/features/permissions/qoder-permissions.ts
|
|
65231
65634
|
/** Top-level key of `.qoder/settings.json` that rulesync owns. */
|
|
65232
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";
|
|
65233
65643
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
65234
65644
|
/** Prefix of the fully qualified MCP tool names (`mcp__<server>__<tool>`). */
|
|
65235
65645
|
const MCP_TOOL_PREFIX = "mcp__";
|
|
@@ -65389,9 +65799,11 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
|
|
|
65389
65799
|
* through the shared-config gateway, the sibling keys of `permissions`
|
|
65390
65800
|
* (`additionalDirectories`, `trustDirectories`) are kept, and the file is
|
|
65391
65801
|
* never deleted. `.qoder/settings.local.json` (where `/allow` and `/deny`
|
|
65392
|
-
* 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.
|
|
65393
65804
|
*
|
|
65394
65805
|
* @see https://docs.qoder.com/en/cli/permissions
|
|
65806
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
65395
65807
|
*/
|
|
65396
65808
|
var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
65397
65809
|
constructor(params) {
|
|
@@ -65430,11 +65842,14 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65430
65842
|
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
65431
65843
|
});
|
|
65432
65844
|
const existingPermissions = isRecord$1(existing[QODER_PERMISSIONS_KEY]) ? existing[QODER_PERMISSIONS_KEY] : {};
|
|
65845
|
+
const config = rulesyncPermissions.getJson();
|
|
65433
65846
|
const lists = buildQoderRuleLists({
|
|
65434
|
-
config
|
|
65847
|
+
config,
|
|
65435
65848
|
existingPermissions,
|
|
65436
65849
|
logger
|
|
65437
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.`);
|
|
65438
65853
|
const permissions = { ...existingPermissions };
|
|
65439
65854
|
for (const action of ACTIONS) if (lists[action].length > 0) permissions[action] = lists[action];
|
|
65440
65855
|
else delete permissions[action];
|
|
@@ -65446,7 +65861,10 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65446
65861
|
fileKey: sharedConfigFileKey(paths),
|
|
65447
65862
|
feature: "permissions",
|
|
65448
65863
|
existingContent,
|
|
65449
|
-
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
|
+
},
|
|
65450
65868
|
filePath
|
|
65451
65869
|
}),
|
|
65452
65870
|
validate: true,
|
|
@@ -65467,7 +65885,12 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
|
|
|
65467
65885
|
const existing = rules[parsed.pattern];
|
|
65468
65886
|
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) rules[parsed.pattern] = action;
|
|
65469
65887
|
}
|
|
65470
|
-
|
|
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) });
|
|
65471
65894
|
}
|
|
65472
65895
|
validate() {
|
|
65473
65896
|
return {
|
|
@@ -84515,8 +84938,8 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84515
84938
|
* Derive the subagent name from this instance's relative file path.
|
|
84516
84939
|
*
|
|
84517
84940
|
* The tool-side layout is `<name>/AGENT.md`, so the name is the parent
|
|
84518
|
-
* directory of the file.
|
|
84519
|
-
*
|
|
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.
|
|
84520
84943
|
*/
|
|
84521
84944
|
getSubagentName() {
|
|
84522
84945
|
const relativeFilePath = this.getRelativeFilePath();
|
|
@@ -84609,8 +85032,9 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84609
85032
|
/**
|
|
84610
85033
|
* Subagent inside a Devin plugin bundle (`<plugin>/agents/<name>/AGENT.md`).
|
|
84611
85034
|
* Plugin subagents use the same profile format as project ones and are
|
|
84612
|
-
* exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md
|
|
84613
|
-
* 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.
|
|
84614
85038
|
*
|
|
84615
85039
|
* @see https://docs.devin.ai/cli/extensibility/plugins/overview
|
|
84616
85040
|
*/
|
|
@@ -88172,6 +88596,17 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
88172
88596
|
};
|
|
88173
88597
|
//#endregion
|
|
88174
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
|
+
}
|
|
88175
88610
|
const SubagentsProcessorToolTargetSchema = zod_mini.z.enum(subagentsProcessorToolTargetTuple);
|
|
88176
88611
|
/**
|
|
88177
88612
|
* Factory Map mapping tool targets to their subagent factories.
|
|
@@ -88368,7 +88803,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
88368
88803
|
supportsProject: true,
|
|
88369
88804
|
supportsSimulated: false,
|
|
88370
88805
|
supportsGlobal: true,
|
|
88371
|
-
filePattern: "*/AGENT.md"
|
|
88806
|
+
filePattern: "*/AGENT.md",
|
|
88807
|
+
importFilePatterns: ["*.md", "*/AGENT.md"],
|
|
88808
|
+
skipInvalidImportFiles: true
|
|
88372
88809
|
}
|
|
88373
88810
|
}],
|
|
88374
88811
|
["devin-plugin", {
|
|
@@ -88377,7 +88814,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
88377
88814
|
supportsProject: true,
|
|
88378
88815
|
supportsSimulated: false,
|
|
88379
88816
|
supportsGlobal: false,
|
|
88380
|
-
filePattern: "*/AGENT.md"
|
|
88817
|
+
filePattern: "*/AGENT.md",
|
|
88818
|
+
importFilePatterns: ["*.md", "*/AGENT.md"],
|
|
88819
|
+
skipInvalidImportFiles: true
|
|
88381
88820
|
}
|
|
88382
88821
|
}],
|
|
88383
88822
|
["factorydroid", {
|
|
@@ -88856,6 +89295,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
88856
89295
|
const factory = this.getFactory(this.toolTarget);
|
|
88857
89296
|
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
88858
89297
|
const roots = forDeletion ? [paths.relativeDirPath] : [paths.relativeDirPath, ...paths.importDirPaths ?? []];
|
|
89298
|
+
const filePatterns = forDeletion ? factory.meta.filePattern : importFilePatternsOf(factory.meta);
|
|
88859
89299
|
const toolSubagents = [];
|
|
88860
89300
|
const claimedRelativeFilePaths = /* @__PURE__ */ new Map();
|
|
88861
89301
|
const claimedImportIdentities = new ClaimedIdentities();
|
|
@@ -88872,7 +89312,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
88872
89312
|
this.logger.warn(`Skipping the orphan sweep for ${quoteForLog(baseDir)}; nothing under it is deleted: ${stripControlCharacters(formatError(error))}`);
|
|
88873
89313
|
continue;
|
|
88874
89314
|
}
|
|
88875
|
-
const subagentFilePaths = (await findFilesByGlobs(
|
|
89315
|
+
const subagentFilePaths = (await findFilesByGlobs(filePatterns, {
|
|
88876
89316
|
cwd: baseDir,
|
|
88877
89317
|
followSymbolicLinks: !forDeletion
|
|
88878
89318
|
})).toSorted((left, right) => compareRelativePathsShallowestFirst((0, node_path.relative)(baseDir, left), (0, node_path.relative)(baseDir, right)));
|
|
@@ -88886,7 +89326,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
88886
89326
|
})));
|
|
88887
89327
|
ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
|
|
88888
89328
|
}
|
|
88889
|
-
const parsedFiles = factory.meta
|
|
89329
|
+
const parsedFiles = skipsInvalidFiles(factory.meta, forDeletion) ? (await Promise.all(ownedFilePaths.map(async (path) => {
|
|
88890
89330
|
try {
|
|
88891
89331
|
return {
|
|
88892
89332
|
path,
|
|
@@ -91762,6 +92202,85 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
91762
92202
|
}
|
|
91763
92203
|
};
|
|
91764
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
|
|
91765
92284
|
//#region src/features/rules/codewhale-rule.ts
|
|
91766
92285
|
/**
|
|
91767
92286
|
* Rule generator for Codewhale.
|
|
@@ -93399,7 +93918,7 @@ var DshRule = class extends NestedAgentsmdRule {
|
|
|
93399
93918
|
* `getExtraFixedFiles`): Droid loads "direct `.md` children of each
|
|
93400
93919
|
* `output-styles` directory".
|
|
93401
93920
|
*/
|
|
93402
|
-
const OUTPUT_STYLE_FILE_GLOB = "*.md";
|
|
93921
|
+
const OUTPUT_STYLE_FILE_GLOB$1 = "*.md";
|
|
93403
93922
|
/**
|
|
93404
93923
|
* Rule generator for Factory Droid.
|
|
93405
93924
|
*
|
|
@@ -93521,7 +94040,7 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
93521
94040
|
static getExtraFixedFiles({ global = false } = {}) {
|
|
93522
94041
|
return [...this.getChannelPaths({ global }).map(({ path }) => path), {
|
|
93523
94042
|
relativeDirPath: this.getOutputStylesDirPath(),
|
|
93524
|
-
relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
|
|
94043
|
+
relativeFilePath: OUTPUT_STYLE_FILE_GLOB$1,
|
|
93525
94044
|
importOnly: true
|
|
93526
94045
|
}];
|
|
93527
94046
|
}
|
|
@@ -95376,6 +95895,12 @@ const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set([
|
|
|
95376
95895
|
* Any other mode is kept in the `qoder` section so it survives a round trip.
|
|
95377
95896
|
*/
|
|
95378
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";
|
|
95379
95904
|
function normalizeGlobList(value) {
|
|
95380
95905
|
if (value === void 0) return [];
|
|
95381
95906
|
return (typeof value === "string" ? splitBraceAwareList(value) : value).map((glob) => glob.trim()).filter((glob) => glob !== "");
|
|
@@ -95409,22 +95934,59 @@ function resolveTrigger(frontmatter) {
|
|
|
95409
95934
|
* A `qoder.trigger` (for example `model_decision` or `manual`) always takes
|
|
95410
95935
|
* precedence and round-trips through import.
|
|
95411
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
|
+
*
|
|
95412
95942
|
* @see https://docs.qoder.com/en/cli/04-extending-qoder-cli/memory
|
|
95943
|
+
* @see https://docs.qoder.com/cli/output-styles
|
|
95413
95944
|
*/
|
|
95414
95945
|
var QoderRule = class QoderRule extends ToolRule {
|
|
95415
95946
|
frontmatter;
|
|
95416
95947
|
body;
|
|
95417
|
-
|
|
95948
|
+
outputStyle;
|
|
95949
|
+
constructor({ frontmatter, body, outputStyle = false, ...rest }) {
|
|
95418
95950
|
if (rest.validate) {
|
|
95419
95951
|
const result = QoderRuleFrontmatterSchema.safeParse(frontmatter);
|
|
95420
95952
|
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
95421
95953
|
}
|
|
95422
95954
|
super({
|
|
95423
95955
|
...rest,
|
|
95424
|
-
fileContent: rest.root ? body : stringifyFrontmatter(body, frontmatter)
|
|
95956
|
+
fileContent: rest.root || outputStyle && Object.keys(frontmatter).length === 0 ? body : stringifyFrontmatter(body, frontmatter)
|
|
95425
95957
|
});
|
|
95426
95958
|
this.frontmatter = frontmatter;
|
|
95427
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;
|
|
95428
95990
|
}
|
|
95429
95991
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
95430
95992
|
return {
|
|
@@ -95437,6 +95999,23 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95437
95999
|
}
|
|
95438
96000
|
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
95439
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
|
+
}
|
|
95440
96019
|
if (relativeDirPath !== void 0 ? relativeDirPath !== QODER_RULES_DIR_PATH : relativeFilePath === paths.root.relativeFilePath) {
|
|
95441
96020
|
const rootDirPath = relativeDirPath ?? paths.root.relativeDirPath;
|
|
95442
96021
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootDirPath, paths.root.relativeFilePath));
|
|
@@ -95469,6 +96048,11 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95469
96048
|
const root = rulesyncFrontmatter.root ?? false;
|
|
95470
96049
|
const paths = this.getSettablePaths({ global });
|
|
95471
96050
|
const body = rulesyncRule.getBody();
|
|
96051
|
+
if (this.isOutputStyleRule(rulesyncRule)) return this.buildOutputStyle({
|
|
96052
|
+
outputRoot,
|
|
96053
|
+
rulesyncRule,
|
|
96054
|
+
validate
|
|
96055
|
+
});
|
|
95472
96056
|
if (root) return new QoderRule({
|
|
95473
96057
|
outputRoot,
|
|
95474
96058
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -95488,8 +96072,32 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95488
96072
|
root: false
|
|
95489
96073
|
});
|
|
95490
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
|
+
}
|
|
95491
96099
|
static buildFrontmatter(rulesyncFrontmatter, relativeFilePath) {
|
|
95492
|
-
const { trigger: storedTrigger, glob: storedGlob, ...section } = rulesyncFrontmatter.qoder ?? {};
|
|
96100
|
+
const { trigger: storedTrigger, glob: storedGlob, channel: _channel, name: _name, ...section } = rulesyncFrontmatter.qoder ?? {};
|
|
95493
96101
|
const specificGlobs = normalizeGlobList(storedGlob ?? rulesyncFrontmatter.globs).filter((glob) => !UNIVERSAL_GLOBS$1.has(glob));
|
|
95494
96102
|
let trigger = storedTrigger ?? (specificGlobs.length > 0 ? "glob" : "always_on");
|
|
95495
96103
|
const description = section.description ?? rulesyncFrontmatter.description;
|
|
@@ -95507,6 +96115,25 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95507
96115
|
}
|
|
95508
96116
|
toRulesyncRule() {
|
|
95509
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
|
+
}
|
|
95510
96137
|
const trigger = resolveTrigger(this.frontmatter);
|
|
95511
96138
|
const { trigger: _trigger, alwaysApply: _alwaysApply, description, glob, paths, ...unmapped } = this.frontmatter;
|
|
95512
96139
|
const fileGlobs = normalizeGlobList(glob ?? paths);
|
|
@@ -95555,6 +96182,10 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95555
96182
|
return this.body;
|
|
95556
96183
|
}
|
|
95557
96184
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
96185
|
+
const outputStyle = this.isOutputStylePath({
|
|
96186
|
+
relativeDirPath,
|
|
96187
|
+
relativeFilePath
|
|
96188
|
+
});
|
|
95558
96189
|
return new QoderRule({
|
|
95559
96190
|
outputRoot,
|
|
95560
96191
|
relativeDirPath,
|
|
@@ -95562,7 +96193,8 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
95562
96193
|
frontmatter: {},
|
|
95563
96194
|
body: "",
|
|
95564
96195
|
validate: false,
|
|
95565
|
-
root: relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md"
|
|
96196
|
+
root: !outputStyle && relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md",
|
|
96197
|
+
outputStyle
|
|
95566
96198
|
});
|
|
95567
96199
|
}
|
|
95568
96200
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -97249,6 +97881,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
97249
97881
|
localRootFileName: CODEBUDDY_LOCAL_RULE_FILE_NAME
|
|
97250
97882
|
}
|
|
97251
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
|
+
}],
|
|
97252
97894
|
["codewhale", {
|
|
97253
97895
|
class: CodewhaleRule,
|
|
97254
97896
|
meta: {
|
|
@@ -97679,7 +98321,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
97679
98321
|
}]
|
|
97680
98322
|
]);
|
|
97681
98323
|
const allToolTargetKeys = [...toolRuleFactories.keys()];
|
|
97682
|
-
const rulesProcessorToolTargets = allToolTargetKeys
|
|
98324
|
+
const rulesProcessorToolTargets = allToolTargetKeys.filter((target) => {
|
|
98325
|
+
return toolRuleFactories.get(target)?.meta.supportsProject !== false;
|
|
98326
|
+
});
|
|
97683
98327
|
const rulesProcessorToolTargetsGlobal = allToolTargetKeys.filter((target) => {
|
|
97684
98328
|
return toolRuleFactories.get(target)?.meta.supportsGlobal ?? false;
|
|
97685
98329
|
});
|
|
@@ -97758,7 +98402,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
97758
98402
|
const factory = this.getFactory(this.toolTarget);
|
|
97759
98403
|
const { meta } = factory;
|
|
97760
98404
|
this.warnForIncludeRootOption(meta);
|
|
97761
|
-
this.
|
|
98405
|
+
this.warnForOutputStyleRuleOptions({
|
|
97762
98406
|
rules: alignedRules,
|
|
97763
98407
|
factory
|
|
97764
98408
|
});
|
|
@@ -97992,22 +98636,25 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
97992
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.");
|
|
97993
98637
|
}
|
|
97994
98638
|
/**
|
|
97995
|
-
* Warn about `factorydroid` frontmatter that does not do what it
|
|
97996
|
-
* it does: an `output-style` rule that also targets other tools
|
|
97997
|
-
* as an ordinary rule (so presentation instructions end up in
|
|
97998
|
-
* rules), and
|
|
97999
|
-
* 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.
|
|
98000
98645
|
*/
|
|
98001
|
-
|
|
98002
|
-
|
|
98646
|
+
warnForOutputStyleRuleOptions({ rules, factory }) {
|
|
98647
|
+
const toolTarget = this.toolTarget;
|
|
98648
|
+
if (toolTarget !== "factorydroid" && toolTarget !== "qoder") return;
|
|
98003
98649
|
for (const rule of rules) {
|
|
98004
98650
|
const frontmatter = rule.getFrontmatter();
|
|
98005
|
-
|
|
98006
|
-
|
|
98651
|
+
const section = frontmatter[toolTarget];
|
|
98652
|
+
if (frontmatter.root || !section || !factory.class.isTargetedByRulesyncRule(rule)) continue;
|
|
98653
|
+
const { channel, name } = section;
|
|
98007
98654
|
const fileName = stripControlCharacters(rule.getRelativeFilePath());
|
|
98008
98655
|
const targets = frontmatter.targets ?? ["*"];
|
|
98009
|
-
if (channel === "output-style" && targets.some((target) => target !==
|
|
98010
|
-
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.`);
|
|
98011
98658
|
}
|
|
98012
98659
|
}
|
|
98013
98660
|
/**
|
|
@@ -102454,6 +103101,12 @@ Object.defineProperty(exports, "hasEnclosingMarkOutsideKeycap", {
|
|
|
102454
103101
|
return hasEnclosingMarkOutsideKeycap;
|
|
102455
103102
|
}
|
|
102456
103103
|
});
|
|
103104
|
+
Object.defineProperty(exports, "hasExecutableBit", {
|
|
103105
|
+
enumerable: true,
|
|
103106
|
+
get: function() {
|
|
103107
|
+
return hasExecutableBit;
|
|
103108
|
+
}
|
|
103109
|
+
});
|
|
102457
103110
|
Object.defineProperty(exports, "importFromTool", {
|
|
102458
103111
|
enumerable: true,
|
|
102459
103112
|
get: function() {
|
|
@@ -102610,6 +103263,12 @@ Object.defineProperty(exports, "resolveRulesyncSourceWritePath", {
|
|
|
102610
103263
|
return resolveRulesyncSourceWritePath;
|
|
102611
103264
|
}
|
|
102612
103265
|
});
|
|
103266
|
+
Object.defineProperty(exports, "resolvedPathEscapesRoot", {
|
|
103267
|
+
enumerable: true,
|
|
103268
|
+
get: function() {
|
|
103269
|
+
return resolvedPathEscapesRoot;
|
|
103270
|
+
}
|
|
103271
|
+
});
|
|
102613
103272
|
Object.defineProperty(exports, "runWithDirectoryRollback", {
|
|
102614
103273
|
enumerable: true,
|
|
102615
103274
|
get: function() {
|