rulesync 43.1.0 → 43.1.2

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.
@@ -15889,17 +15889,24 @@ function toolSkillSearchRoots(paths) {
15889
15889
  return [paths.relativeDirPath, ...paths.alternativeSkillRoots ?? []];
15890
15890
  }
15891
15891
  /**
15892
- * The Agent Skills interoperability roots: project `.agents/skills/`, global
15893
- * `~/.agents/skills/` (same relative path, home output root), and Amp's global
15894
- * equivalent `~/.config/agents/skills/`. Foreign-authored — potentially
15895
- * non-conformant — skills are the most likely to live here, so every tool that
15896
- * scans one of these roots imports it leniently (skip-and-warn per skill), per
15897
- * the Agent Skills client-implementation guide, while keeping fail-fast
15898
- * behavior for its own native root. Amp's read-only global discovery root
15899
- * `~/.config/amp/skills/` follows the same skip-and-warn rule.
15900
- * https://agentskills.io/client-implementation/adding-skills-support
15901
- */
15902
- const AGENT_SKILLS_INTEROP_ROOTS = /* @__PURE__ */ new Set([
15892
+ * Skill roots that every tool scanning them imports leniently (skip-and-warn
15893
+ * per skill) instead of failing fast, regardless of the tool's own
15894
+ * `lenientImport` flag.
15895
+ *
15896
+ * - The Agent Skills interoperability roots: project `.agents/skills/`, global
15897
+ * `~/.agents/skills/` (same relative path, home output root), and Amp's
15898
+ * global equivalent `~/.config/agents/skills/`. Foreign-authored —
15899
+ * potentially non-conformant — skills are the most likely to live here, so
15900
+ * they are imported leniently per the Agent Skills client-implementation
15901
+ * guide, while each tool keeps fail-fast behavior for its own native root.
15902
+ * https://agentskills.io/client-implementation/adding-skills-support
15903
+ * - Amp's read-only global discovery root `~/.config/amp/skills/`, which is not
15904
+ * an interop root but follows the same skip-and-warn rule.
15905
+ *
15906
+ * Other import-only roots (e.g. GitLab Duo's legacy skills root, Kimi Code's
15907
+ * shared root) are not listed here and stay fail-fast.
15908
+ */
15909
+ const LENIENT_IMPORT_SKILL_ROOTS = /* @__PURE__ */ new Set([
15903
15910
  toPosixPath(AGENTSMD_SKILLS_DIR_PATH),
15904
15911
  toPosixPath(AMP_SKILLS_GLOBAL_DIR),
15905
15912
  toPosixPath(AMP_SKILLS_NATIVE_GLOBAL_DIR)
@@ -15924,8 +15931,8 @@ function warnOnEmptyLoadedDescription({ skillFilePath, description }) {
15924
15931
  if (typeof description !== "string" || description.length > 0) return;
15925
15932
  warnOnceWithFallback(void 0, `${stripControlCharacters(toPosixPath(skillFilePath))}: ${EMPTY_SKILL_DESCRIPTION_VIOLATION}. Rulesync imports it anyway so the content is not lost; fill it in before relying on this skill.`);
15926
15933
  }
15927
- function isAgentSkillsInteropRoot(relativeDirPath) {
15928
- return AGENT_SKILLS_INTEROP_ROOTS.has(toPosixPath(relativeDirPath));
15934
+ function isLenientImportSkillRoot(relativeDirPath) {
15935
+ return LENIENT_IMPORT_SKILL_ROOTS.has(toPosixPath(relativeDirPath));
15929
15936
  }
15930
15937
  /** Ordered import roots: managed roots first, followed by read-only discovery roots. */
15931
15938
  function toolSkillImportRoots(paths) {
@@ -33155,7 +33162,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
33155
33162
  for (const root of roots) {
33156
33163
  const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
33157
33164
  const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
33158
- const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isAgentSkillsInteropRoot(relativeDirPath);
33165
+ const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isLenientImportSkillRoot(relativeDirPath);
33159
33166
  const skillsDirPath = (0, node_path.join)(rootOutputRoot, relativeDirPath);
33160
33167
  if (!await directoryExists(skillsDirPath)) continue;
33161
33168
  const ownedDirNames = [];
@@ -55844,7 +55851,7 @@ function isFieldApplicable({ commandOnly, hookType }) {
55844
55851
  * silence — the mirror of the import side.
55845
55852
  */
55846
55853
  function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn }) {
55847
- for (const { canonical, tool, commandOnly, maxLength } of fields) {
55854
+ for (const { canonical, tool, commandOnly, maxLength, collapseWhitespaceForLength, onlyWithFlag } of fields) {
55848
55855
  const value = def[canonical];
55849
55856
  if (value === void 0) continue;
55850
55857
  if (!isFieldApplicable({
@@ -55861,7 +55868,11 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
55861
55868
  warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": ${quoteValueForWarning(value)} is not a value this tool can express as "${tool}".`);
55862
55869
  continue;
55863
55870
  }
55864
- if (maxLength !== void 0 && typeof value === "string" && value.length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${value.length} characters long; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
55871
+ if (onlyWithFlag !== void 0 && def[onlyWithFlag] !== true) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" has no effect without "${onlyWithFlag}": true; this tool reads "${tool}" only on "${onlyWithFlag}" hooks.`);
55872
+ if (maxLength !== void 0 && typeof value === "string") {
55873
+ const length = collapseWhitespaceForLength ? value.replace(/\s+/g, " ").trim().length : value.length;
55874
+ if (length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${length} characters long${collapseWhitespaceForLength ? " after collapsing whitespace" : ""}; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
55875
+ }
55865
55876
  }
55866
55877
  return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
55867
55878
  commandOnly,
@@ -64261,13 +64272,16 @@ const QODER_SPEC = {
64261
64272
  {
64262
64273
  canonical: "rewakeMessage",
64263
64274
  tool: "rewakeMessage",
64264
- commandOnly: true
64275
+ commandOnly: true,
64276
+ onlyWithFlag: "asyncRewake"
64265
64277
  },
64266
64278
  {
64267
64279
  canonical: "rewakeSummary",
64268
64280
  tool: "rewakeSummary",
64269
64281
  commandOnly: true,
64270
- maxLength: 300
64282
+ maxLength: 300,
64283
+ collapseWhitespaceForLength: true,
64284
+ onlyWithFlag: "asyncRewake"
64271
64285
  }
64272
64286
  ],
64273
64287
  booleanPassthroughFields: [
@@ -69647,7 +69661,7 @@ var CodebuddyMcp = class CodebuddyMcp extends ToolMcp {
69647
69661
  }
69648
69662
  };
69649
69663
  // src/features/mcp/codebuff-mcp.ts
69650
- const TOOL_NAME$1 = "codebuff";
69664
+ const TOOL_NAME$2 = "codebuff";
69651
69665
  const MAPPED_KEYS = /* @__PURE__ */ new Set([
69652
69666
  "type",
69653
69667
  "transport",
@@ -69682,14 +69696,14 @@ function toCodebuffEnv({ name, env, logger }) {
69682
69696
  }
69683
69697
  if (value.startsWith("$")) {
69684
69698
  warnAndSkipMcpServer({
69685
- toolName: TOOL_NAME$1,
69699
+ toolName: TOOL_NAME$2,
69686
69700
  serverName: name,
69687
69701
  reason: `env ${quoteValueForWarning(key)} starting with "$" that is not a whole \${VAR} reference, which Codebuff would resolve as a variable name and fail to load the whole mcp.json`,
69688
69702
  logger
69689
69703
  });
69690
69704
  return null;
69691
69705
  }
69692
- if (CANONICAL_ENV_REF.test(value)) logger?.warn(`${TOOL_NAME$1} MCP: env ${quoteValueForWarning(key)} of server ${quoteValueForWarning(name)} embeds a \${VAR} reference; Codebuff only resolves a whole-value \$VAR, so it is written as literal text.`);
69706
+ if (CANONICAL_ENV_REF.test(value)) logger?.warn(`${TOOL_NAME$2} MCP: env ${quoteValueForWarning(key)} of server ${quoteValueForWarning(name)} embeds a \${VAR} reference; Codebuff only resolves a whole-value \$VAR, so it is written as literal text.`);
69693
69707
  converted.push([key, value]);
69694
69708
  }
69695
69709
  return Object.fromEntries(converted);
@@ -69706,31 +69720,31 @@ function toCodebuffEnv({ name, env, logger }) {
69706
69720
  */
69707
69721
  function toCodebuffServer({ name, server, logger }) {
69708
69722
  if (server.disabled === true) return warnAndSkipMcpServer({
69709
- toolName: TOOL_NAME$1,
69723
+ toolName: TOOL_NAME$2,
69710
69724
  serverName: name,
69711
69725
  reason: "disabled: true, which Codebuff has no per-server switch for",
69712
69726
  logger
69713
69727
  });
69714
69728
  const transport = server.type ?? server.transport;
69715
69729
  if (transport === "ws") return warnAndSkipMcpServer({
69716
- toolName: TOOL_NAME$1,
69730
+ toolName: TOOL_NAME$2,
69717
69731
  serverName: name,
69718
69732
  reason: "the WebSocket transport, which Codebuff does not support",
69719
69733
  logger
69720
69734
  });
69721
69735
  const dropped = Object.keys(server).filter((key) => !MAPPED_KEYS.has(key));
69722
- if (dropped.length > 0) logger?.warn(`${TOOL_NAME$1} MCP: dropping ${dropped.map((key) => quoteValueForWarning(key)).join(", ")} from server ${quoteValueForWarning(name)}; Codebuff rejects keys outside its server schema.`);
69736
+ if (dropped.length > 0) logger?.warn(`${TOOL_NAME$2} MCP: dropping ${dropped.map((key) => quoteValueForWarning(key)).join(", ")} from server ${quoteValueForWarning(name)}; Codebuff rejects keys outside its server schema.`);
69723
69737
  if (isRemoteMcpServer(server)) {
69724
69738
  const url = resolveRemoteMcpUrl(server);
69725
69739
  if (url === void 0) return warnAndSkipMcpServer({
69726
- toolName: TOOL_NAME$1,
69740
+ toolName: TOOL_NAME$2,
69727
69741
  serverName: name,
69728
69742
  reason: "a remote transport without a url",
69729
69743
  logger
69730
69744
  });
69731
69745
  const params = server.params;
69732
69746
  const validParams = isRecord(params) && Object.values(params).every((value) => typeof value === "string");
69733
- if (params !== void 0 && !validParams) logger?.warn(`${TOOL_NAME$1} MCP: dropping params from server ${quoteValueForWarning(name)}; Codebuff accepts only a map of string values.`);
69747
+ if (params !== void 0 && !validParams) logger?.warn(`${TOOL_NAME$2} MCP: dropping params from server ${quoteValueForWarning(name)}; Codebuff accepts only a map of string values.`);
69734
69748
  return {
69735
69749
  type: transport === "sse" ? "sse" : "http",
69736
69750
  url,
@@ -69740,7 +69754,7 @@ function toCodebuffServer({ name, server, logger }) {
69740
69754
  }
69741
69755
  const [command, ...args] = resolveLocalMcpCommand(server);
69742
69756
  if (command === void 0) return warnAndSkipMcpServer({
69743
- toolName: TOOL_NAME$1,
69757
+ toolName: TOOL_NAME$2,
69744
69758
  serverName: name,
69745
69759
  reason: "no command to spawn",
69746
69760
  logger
@@ -72410,7 +72424,7 @@ var CursorMcp = class CursorMcp extends ToolMcp {
72410
72424
  }
72411
72425
  };
72412
72426
  // src/features/mcp/deepagents-mcp.ts
72413
- const TOOL_NAME = "deepagents";
72427
+ const TOOL_NAME$1 = "deepagents";
72414
72428
  /**
72415
72429
  * Map a canonical transport onto the three dcode accepts.
72416
72430
  *
@@ -72451,7 +72465,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72451
72465
  return true;
72452
72466
  }
72453
72467
  warnAndSkipMcpServer({
72454
- toolName: TOOL_NAME,
72468
+ toolName: TOOL_NAME$1,
72455
72469
  serverName: name,
72456
72470
  reason: "an empty command list, which leaves deepagents nothing to spawn",
72457
72471
  logger
@@ -72479,7 +72493,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72479
72493
  */
72480
72494
  function toDeepagentsOauth({ name, oauth, converted, logger }) {
72481
72495
  if (!isSlackOauthServer(converted)) {
72482
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents reads it only on a Slack (slack.com) server with auth: "oauth" and skips any other server that sets it.`);
72496
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth on "${name}"; deepagents reads it only on a Slack (slack.com) server with auth: "oauth" and skips any other server that sets it.`);
72483
72497
  return;
72484
72498
  }
72485
72499
  const source = isRecord(oauth) ? oauth : {};
@@ -72488,18 +72502,18 @@ function toDeepagentsOauth({ name, oauth, converted, logger }) {
72488
72502
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
72489
72503
  const nativeKey = key === "clientId" ? "client_id" : key;
72490
72504
  if (nativeKey !== "client_id" && nativeKey !== "client_secret") {
72491
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
72505
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
72492
72506
  continue;
72493
72507
  }
72494
72508
  if (nativeKey !== key && isDeepagentsOauthValue(source[nativeKey])) continue;
72495
72509
  if (!isDeepagentsOauthValue(value)) {
72496
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
72510
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
72497
72511
  continue;
72498
72512
  }
72499
72513
  result[nativeKey] = value;
72500
72514
  }
72501
72515
  if (result.client_id === void 0) {
72502
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
72516
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
72503
72517
  return;
72504
72518
  }
72505
72519
  return result;
@@ -72528,14 +72542,14 @@ function isSlackOauthServer(server) {
72528
72542
  */
72529
72543
  function toDeepagentsServer({ name, server, logger }) {
72530
72544
  if (server.disabled === true) return warnAndSkipMcpServer({
72531
- toolName: TOOL_NAME,
72545
+ toolName: TOOL_NAME$1,
72532
72546
  serverName: name,
72533
72547
  reason: "disabled: true, which deepagents does not read from .mcp.json (the server would still run)",
72534
72548
  logger
72535
72549
  });
72536
72550
  const rawTransport = server.transport ?? server.type;
72537
72551
  if (rawTransport === "ws") return warnAndSkipMcpServer({
72538
- toolName: TOOL_NAME,
72552
+ toolName: TOOL_NAME$1,
72539
72553
  serverName: name,
72540
72554
  reason: "the WebSocket transport, which deepagents does not support",
72541
72555
  logger
@@ -72565,21 +72579,21 @@ function toDeepagentsServer({ name, server, logger }) {
72565
72579
  if (deepagentsOauth !== void 0) converted.oauth = deepagentsOauth;
72566
72580
  }
72567
72581
  if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
72568
- toolName: TOOL_NAME,
72582
+ toolName: TOOL_NAME$1,
72569
72583
  serverName: name,
72570
72584
  reason: "both enabledTools and disabledTools, which deepagents rejects — pick one",
72571
72585
  logger
72572
72586
  });
72573
72587
  if (enabledTools !== void 0) {
72574
72588
  if (enabledTools.length === 0) return warnAndSkipMcpServer({
72575
- toolName: TOOL_NAME,
72589
+ toolName: TOOL_NAME$1,
72576
72590
  serverName: name,
72577
72591
  reason: "an empty enabledTools list, which allows no tools at all and which deepagents rejects",
72578
72592
  logger
72579
72593
  });
72580
72594
  converted.allowedTools = enabledTools;
72581
72595
  } else if (disabledTools !== void 0) {
72582
- if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
72596
+ if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME$1} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
72583
72597
  else converted.disabledTools = disabledTools;
72584
72598
  }
72585
72599
  return converted;
@@ -73231,6 +73245,32 @@ var DshMcp = class DshMcp extends ToolMcp {
73231
73245
  }
73232
73246
  };
73233
73247
  // src/features/mcp/factorydroid-mcp.ts
73248
+ const TOOL_NAME = "factorydroid";
73249
+ /**
73250
+ * Drop servers whose `enabledTools` allowlist is empty.
73251
+ *
73252
+ * Canonically an empty allowlist allows no tools, but Droid applies
73253
+ * `enabledTools` only "when the list is non-empty"
73254
+ * (https://docs.factory.com/harness/mcp), so writing `[]` would load every
73255
+ * tool the server reports. Skipping the server is the only form that does not
73256
+ * hand the model more tools than the canonical config allows.
73257
+ */
73258
+ function skipEmptyAllowlistServers({ mcpServers, logger }) {
73259
+ const kept = {};
73260
+ for (const [name, server] of Object.entries(mcpServers)) {
73261
+ if (server.enabledTools?.length === 0) {
73262
+ warnAndSkipMcpServer({
73263
+ toolName: TOOL_NAME,
73264
+ serverName: name,
73265
+ reason: "an empty enabledTools list, which allows no tools at all but which Factory Droid treats as no filter",
73266
+ logger
73267
+ });
73268
+ continue;
73269
+ }
73270
+ kept[name] = server;
73271
+ }
73272
+ return kept;
73273
+ }
73234
73274
  var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73235
73275
  json;
73236
73276
  constructor(params) {
@@ -73256,8 +73296,11 @@ var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73256
73296
  validate
73257
73297
  });
73258
73298
  }
73259
- static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
73260
- const factorydroidConfig = { mcpServers: rulesyncMcp.getMcpServers() };
73299
+ static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, logger }) {
73300
+ const factorydroidConfig = { mcpServers: skipEmptyAllowlistServers({
73301
+ mcpServers: rulesyncMcp.getMcpServers(),
73302
+ logger
73303
+ }) };
73261
73304
  const fileContent = JSON.stringify(factorydroidConfig, null, 2);
73262
73305
  return new FactorydroidMcp({
73263
73306
  outputRoot,
@@ -15867,17 +15867,24 @@ function toolSkillSearchRoots(paths) {
15867
15867
  return [paths.relativeDirPath, ...paths.alternativeSkillRoots ?? []];
15868
15868
  }
15869
15869
  /**
15870
- * The Agent Skills interoperability roots: project `.agents/skills/`, global
15871
- * `~/.agents/skills/` (same relative path, home output root), and Amp's global
15872
- * equivalent `~/.config/agents/skills/`. Foreign-authored — potentially
15873
- * non-conformant — skills are the most likely to live here, so every tool that
15874
- * scans one of these roots imports it leniently (skip-and-warn per skill), per
15875
- * the Agent Skills client-implementation guide, while keeping fail-fast
15876
- * behavior for its own native root. Amp's read-only global discovery root
15877
- * `~/.config/amp/skills/` follows the same skip-and-warn rule.
15878
- * https://agentskills.io/client-implementation/adding-skills-support
15879
- */
15880
- const AGENT_SKILLS_INTEROP_ROOTS = /* @__PURE__ */ new Set([
15870
+ * Skill roots that every tool scanning them imports leniently (skip-and-warn
15871
+ * per skill) instead of failing fast, regardless of the tool's own
15872
+ * `lenientImport` flag.
15873
+ *
15874
+ * - The Agent Skills interoperability roots: project `.agents/skills/`, global
15875
+ * `~/.agents/skills/` (same relative path, home output root), and Amp's
15876
+ * global equivalent `~/.config/agents/skills/`. Foreign-authored —
15877
+ * potentially non-conformant — skills are the most likely to live here, so
15878
+ * they are imported leniently per the Agent Skills client-implementation
15879
+ * guide, while each tool keeps fail-fast behavior for its own native root.
15880
+ * https://agentskills.io/client-implementation/adding-skills-support
15881
+ * - Amp's read-only global discovery root `~/.config/amp/skills/`, which is not
15882
+ * an interop root but follows the same skip-and-warn rule.
15883
+ *
15884
+ * Other import-only roots (e.g. GitLab Duo's legacy skills root, Kimi Code's
15885
+ * shared root) are not listed here and stay fail-fast.
15886
+ */
15887
+ const LENIENT_IMPORT_SKILL_ROOTS = /* @__PURE__ */ new Set([
15881
15888
  toPosixPath(AGENTSMD_SKILLS_DIR_PATH),
15882
15889
  toPosixPath(AMP_SKILLS_GLOBAL_DIR),
15883
15890
  toPosixPath(AMP_SKILLS_NATIVE_GLOBAL_DIR)
@@ -15902,8 +15909,8 @@ function warnOnEmptyLoadedDescription({ skillFilePath, description }) {
15902
15909
  if (typeof description !== "string" || description.length > 0) return;
15903
15910
  warnOnceWithFallback(void 0, `${stripControlCharacters(toPosixPath(skillFilePath))}: ${EMPTY_SKILL_DESCRIPTION_VIOLATION}. Rulesync imports it anyway so the content is not lost; fill it in before relying on this skill.`);
15904
15911
  }
15905
- function isAgentSkillsInteropRoot(relativeDirPath) {
15906
- return AGENT_SKILLS_INTEROP_ROOTS.has(toPosixPath(relativeDirPath));
15912
+ function isLenientImportSkillRoot(relativeDirPath) {
15913
+ return LENIENT_IMPORT_SKILL_ROOTS.has(toPosixPath(relativeDirPath));
15907
15914
  }
15908
15915
  /** Ordered import roots: managed roots first, followed by read-only discovery roots. */
15909
15916
  function toolSkillImportRoots(paths) {
@@ -33133,7 +33140,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
33133
33140
  for (const root of roots) {
33134
33141
  const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
33135
33142
  const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
33136
- const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isAgentSkillsInteropRoot(relativeDirPath);
33143
+ const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isLenientImportSkillRoot(relativeDirPath);
33137
33144
  const skillsDirPath = join(rootOutputRoot, relativeDirPath);
33138
33145
  if (!await directoryExists(skillsDirPath)) continue;
33139
33146
  const ownedDirNames = [];
@@ -55822,7 +55829,7 @@ function isFieldApplicable({ commandOnly, hookType }) {
55822
55829
  * silence — the mirror of the import side.
55823
55830
  */
55824
55831
  function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn }) {
55825
- for (const { canonical, tool, commandOnly, maxLength } of fields) {
55832
+ for (const { canonical, tool, commandOnly, maxLength, collapseWhitespaceForLength, onlyWithFlag } of fields) {
55826
55833
  const value = def[canonical];
55827
55834
  if (value === void 0) continue;
55828
55835
  if (!isFieldApplicable({
@@ -55839,7 +55846,11 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
55839
55846
  warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": ${quoteValueForWarning(value)} is not a value this tool can express as "${tool}".`);
55840
55847
  continue;
55841
55848
  }
55842
- if (maxLength !== void 0 && typeof value === "string" && value.length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${value.length} characters long; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
55849
+ if (onlyWithFlag !== void 0 && def[onlyWithFlag] !== true) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" has no effect without "${onlyWithFlag}": true; this tool reads "${tool}" only on "${onlyWithFlag}" hooks.`);
55850
+ if (maxLength !== void 0 && typeof value === "string") {
55851
+ const length = collapseWhitespaceForLength ? value.replace(/\s+/g, " ").trim().length : value.length;
55852
+ if (length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${length} characters long${collapseWhitespaceForLength ? " after collapsing whitespace" : ""}; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
55853
+ }
55843
55854
  }
55844
55855
  return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
55845
55856
  commandOnly,
@@ -64239,13 +64250,16 @@ const QODER_SPEC = {
64239
64250
  {
64240
64251
  canonical: "rewakeMessage",
64241
64252
  tool: "rewakeMessage",
64242
- commandOnly: true
64253
+ commandOnly: true,
64254
+ onlyWithFlag: "asyncRewake"
64243
64255
  },
64244
64256
  {
64245
64257
  canonical: "rewakeSummary",
64246
64258
  tool: "rewakeSummary",
64247
64259
  commandOnly: true,
64248
- maxLength: 300
64260
+ maxLength: 300,
64261
+ collapseWhitespaceForLength: true,
64262
+ onlyWithFlag: "asyncRewake"
64249
64263
  }
64250
64264
  ],
64251
64265
  booleanPassthroughFields: [
@@ -69625,7 +69639,7 @@ var CodebuddyMcp = class CodebuddyMcp extends ToolMcp {
69625
69639
  }
69626
69640
  };
69627
69641
  // src/features/mcp/codebuff-mcp.ts
69628
- const TOOL_NAME$1 = "codebuff";
69642
+ const TOOL_NAME$2 = "codebuff";
69629
69643
  const MAPPED_KEYS = /* @__PURE__ */ new Set([
69630
69644
  "type",
69631
69645
  "transport",
@@ -69660,14 +69674,14 @@ function toCodebuffEnv({ name, env, logger }) {
69660
69674
  }
69661
69675
  if (value.startsWith("$")) {
69662
69676
  warnAndSkipMcpServer({
69663
- toolName: TOOL_NAME$1,
69677
+ toolName: TOOL_NAME$2,
69664
69678
  serverName: name,
69665
69679
  reason: `env ${quoteValueForWarning(key)} starting with "$" that is not a whole \${VAR} reference, which Codebuff would resolve as a variable name and fail to load the whole mcp.json`,
69666
69680
  logger
69667
69681
  });
69668
69682
  return null;
69669
69683
  }
69670
- if (CANONICAL_ENV_REF.test(value)) logger?.warn(`${TOOL_NAME$1} MCP: env ${quoteValueForWarning(key)} of server ${quoteValueForWarning(name)} embeds a \${VAR} reference; Codebuff only resolves a whole-value \$VAR, so it is written as literal text.`);
69684
+ if (CANONICAL_ENV_REF.test(value)) logger?.warn(`${TOOL_NAME$2} MCP: env ${quoteValueForWarning(key)} of server ${quoteValueForWarning(name)} embeds a \${VAR} reference; Codebuff only resolves a whole-value \$VAR, so it is written as literal text.`);
69671
69685
  converted.push([key, value]);
69672
69686
  }
69673
69687
  return Object.fromEntries(converted);
@@ -69684,31 +69698,31 @@ function toCodebuffEnv({ name, env, logger }) {
69684
69698
  */
69685
69699
  function toCodebuffServer({ name, server, logger }) {
69686
69700
  if (server.disabled === true) return warnAndSkipMcpServer({
69687
- toolName: TOOL_NAME$1,
69701
+ toolName: TOOL_NAME$2,
69688
69702
  serverName: name,
69689
69703
  reason: "disabled: true, which Codebuff has no per-server switch for",
69690
69704
  logger
69691
69705
  });
69692
69706
  const transport = server.type ?? server.transport;
69693
69707
  if (transport === "ws") return warnAndSkipMcpServer({
69694
- toolName: TOOL_NAME$1,
69708
+ toolName: TOOL_NAME$2,
69695
69709
  serverName: name,
69696
69710
  reason: "the WebSocket transport, which Codebuff does not support",
69697
69711
  logger
69698
69712
  });
69699
69713
  const dropped = Object.keys(server).filter((key) => !MAPPED_KEYS.has(key));
69700
- if (dropped.length > 0) logger?.warn(`${TOOL_NAME$1} MCP: dropping ${dropped.map((key) => quoteValueForWarning(key)).join(", ")} from server ${quoteValueForWarning(name)}; Codebuff rejects keys outside its server schema.`);
69714
+ if (dropped.length > 0) logger?.warn(`${TOOL_NAME$2} MCP: dropping ${dropped.map((key) => quoteValueForWarning(key)).join(", ")} from server ${quoteValueForWarning(name)}; Codebuff rejects keys outside its server schema.`);
69701
69715
  if (isRemoteMcpServer(server)) {
69702
69716
  const url = resolveRemoteMcpUrl(server);
69703
69717
  if (url === void 0) return warnAndSkipMcpServer({
69704
- toolName: TOOL_NAME$1,
69718
+ toolName: TOOL_NAME$2,
69705
69719
  serverName: name,
69706
69720
  reason: "a remote transport without a url",
69707
69721
  logger
69708
69722
  });
69709
69723
  const params = server.params;
69710
69724
  const validParams = isRecord(params) && Object.values(params).every((value) => typeof value === "string");
69711
- if (params !== void 0 && !validParams) logger?.warn(`${TOOL_NAME$1} MCP: dropping params from server ${quoteValueForWarning(name)}; Codebuff accepts only a map of string values.`);
69725
+ if (params !== void 0 && !validParams) logger?.warn(`${TOOL_NAME$2} MCP: dropping params from server ${quoteValueForWarning(name)}; Codebuff accepts only a map of string values.`);
69712
69726
  return {
69713
69727
  type: transport === "sse" ? "sse" : "http",
69714
69728
  url,
@@ -69718,7 +69732,7 @@ function toCodebuffServer({ name, server, logger }) {
69718
69732
  }
69719
69733
  const [command, ...args] = resolveLocalMcpCommand(server);
69720
69734
  if (command === void 0) return warnAndSkipMcpServer({
69721
- toolName: TOOL_NAME$1,
69735
+ toolName: TOOL_NAME$2,
69722
69736
  serverName: name,
69723
69737
  reason: "no command to spawn",
69724
69738
  logger
@@ -72388,7 +72402,7 @@ var CursorMcp = class CursorMcp extends ToolMcp {
72388
72402
  }
72389
72403
  };
72390
72404
  // src/features/mcp/deepagents-mcp.ts
72391
- const TOOL_NAME = "deepagents";
72405
+ const TOOL_NAME$1 = "deepagents";
72392
72406
  /**
72393
72407
  * Map a canonical transport onto the three dcode accepts.
72394
72408
  *
@@ -72429,7 +72443,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72429
72443
  return true;
72430
72444
  }
72431
72445
  warnAndSkipMcpServer({
72432
- toolName: TOOL_NAME,
72446
+ toolName: TOOL_NAME$1,
72433
72447
  serverName: name,
72434
72448
  reason: "an empty command list, which leaves deepagents nothing to spawn",
72435
72449
  logger
@@ -72457,7 +72471,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72457
72471
  */
72458
72472
  function toDeepagentsOauth({ name, oauth, converted, logger }) {
72459
72473
  if (!isSlackOauthServer(converted)) {
72460
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents reads it only on a Slack (slack.com) server with auth: "oauth" and skips any other server that sets it.`);
72474
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth on "${name}"; deepagents reads it only on a Slack (slack.com) server with auth: "oauth" and skips any other server that sets it.`);
72461
72475
  return;
72462
72476
  }
72463
72477
  const source = isRecord(oauth) ? oauth : {};
@@ -72466,18 +72480,18 @@ function toDeepagentsOauth({ name, oauth, converted, logger }) {
72466
72480
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
72467
72481
  const nativeKey = key === "clientId" ? "client_id" : key;
72468
72482
  if (nativeKey !== "client_id" && nativeKey !== "client_secret") {
72469
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
72483
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
72470
72484
  continue;
72471
72485
  }
72472
72486
  if (nativeKey !== key && isDeepagentsOauthValue(source[nativeKey])) continue;
72473
72487
  if (!isDeepagentsOauthValue(value)) {
72474
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
72488
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
72475
72489
  continue;
72476
72490
  }
72477
72491
  result[nativeKey] = value;
72478
72492
  }
72479
72493
  if (result.client_id === void 0) {
72480
- logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
72494
+ logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
72481
72495
  return;
72482
72496
  }
72483
72497
  return result;
@@ -72506,14 +72520,14 @@ function isSlackOauthServer(server) {
72506
72520
  */
72507
72521
  function toDeepagentsServer({ name, server, logger }) {
72508
72522
  if (server.disabled === true) return warnAndSkipMcpServer({
72509
- toolName: TOOL_NAME,
72523
+ toolName: TOOL_NAME$1,
72510
72524
  serverName: name,
72511
72525
  reason: "disabled: true, which deepagents does not read from .mcp.json (the server would still run)",
72512
72526
  logger
72513
72527
  });
72514
72528
  const rawTransport = server.transport ?? server.type;
72515
72529
  if (rawTransport === "ws") return warnAndSkipMcpServer({
72516
- toolName: TOOL_NAME,
72530
+ toolName: TOOL_NAME$1,
72517
72531
  serverName: name,
72518
72532
  reason: "the WebSocket transport, which deepagents does not support",
72519
72533
  logger
@@ -72543,21 +72557,21 @@ function toDeepagentsServer({ name, server, logger }) {
72543
72557
  if (deepagentsOauth !== void 0) converted.oauth = deepagentsOauth;
72544
72558
  }
72545
72559
  if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
72546
- toolName: TOOL_NAME,
72560
+ toolName: TOOL_NAME$1,
72547
72561
  serverName: name,
72548
72562
  reason: "both enabledTools and disabledTools, which deepagents rejects — pick one",
72549
72563
  logger
72550
72564
  });
72551
72565
  if (enabledTools !== void 0) {
72552
72566
  if (enabledTools.length === 0) return warnAndSkipMcpServer({
72553
- toolName: TOOL_NAME,
72567
+ toolName: TOOL_NAME$1,
72554
72568
  serverName: name,
72555
72569
  reason: "an empty enabledTools list, which allows no tools at all and which deepagents rejects",
72556
72570
  logger
72557
72571
  });
72558
72572
  converted.allowedTools = enabledTools;
72559
72573
  } else if (disabledTools !== void 0) {
72560
- if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
72574
+ if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME$1} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
72561
72575
  else converted.disabledTools = disabledTools;
72562
72576
  }
72563
72577
  return converted;
@@ -73209,6 +73223,32 @@ var DshMcp = class DshMcp extends ToolMcp {
73209
73223
  }
73210
73224
  };
73211
73225
  // src/features/mcp/factorydroid-mcp.ts
73226
+ const TOOL_NAME = "factorydroid";
73227
+ /**
73228
+ * Drop servers whose `enabledTools` allowlist is empty.
73229
+ *
73230
+ * Canonically an empty allowlist allows no tools, but Droid applies
73231
+ * `enabledTools` only "when the list is non-empty"
73232
+ * (https://docs.factory.com/harness/mcp), so writing `[]` would load every
73233
+ * tool the server reports. Skipping the server is the only form that does not
73234
+ * hand the model more tools than the canonical config allows.
73235
+ */
73236
+ function skipEmptyAllowlistServers({ mcpServers, logger }) {
73237
+ const kept = {};
73238
+ for (const [name, server] of Object.entries(mcpServers)) {
73239
+ if (server.enabledTools?.length === 0) {
73240
+ warnAndSkipMcpServer({
73241
+ toolName: TOOL_NAME,
73242
+ serverName: name,
73243
+ reason: "an empty enabledTools list, which allows no tools at all but which Factory Droid treats as no filter",
73244
+ logger
73245
+ });
73246
+ continue;
73247
+ }
73248
+ kept[name] = server;
73249
+ }
73250
+ return kept;
73251
+ }
73212
73252
  var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73213
73253
  json;
73214
73254
  constructor(params) {
@@ -73234,8 +73274,11 @@ var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73234
73274
  validate
73235
73275
  });
73236
73276
  }
73237
- static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
73238
- const factorydroidConfig = { mcpServers: rulesyncMcp.getMcpServers() };
73277
+ static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, logger }) {
73278
+ const factorydroidConfig = { mcpServers: skipEmptyAllowlistServers({
73279
+ mcpServers: rulesyncMcp.getMcpServers(),
73280
+ logger
73281
+ }) };
73239
73282
  const fileContent = JSON.stringify(factorydroidConfig, null, 2);
73240
73283
  return new FactorydroidMcp({
73241
73284
  outputRoot,
@@ -106432,4 +106475,4 @@ async function importChecksCore(params) {
106432
106475
  }
106433
106476
  export { RulesyncPermissions as $, formatError as $n, isSymlink as $t, CODEBUDDY_DIR as A, MAX_FILE_SIZE as An, withFallbackLoggerTarget as At, CLAUDECODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, QWENCODE_LOCAL_RULE_FILE_NAME as C, PACKAGING_TOOL_TARGETS as Cn, getSourceTransport as Ct, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as D, DEPRECATED_FEATURE_REPLACEMENTS as Dn, WarningCollectingLogger as Dt, FACTORYDROID_DIR as E, ALL_FEATURES_WITH_WILDCARD as En, JsonLogger as Et, groupSpellingsByCaseFoldedIdentity as F, RULESYNC_CONFIG_SCHEMA_URL as Fn, PathOutsideRootError as Ft, toInstructionBudgetFile as G, RULESYNC_PERMISSIONS_SCHEMA_URL as Gn, ensureDir as Gt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as H, RULESYNC_MCP_SCHEMA_URL as Hn, checkPathTraversal as Ht, CODEXCLI_BASH_RULES_FILE_NAME as I, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as In, applyFileMode as It, RulesyncSubagentFrontmatterSchema as J, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, getLocalSkillDirNames as K, RULESYNC_RELATIVE_DIR_PATH as Kn, fileExists as Kt, CODEXCLI_DIR as L, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_DIR as M, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Mn, withWarnOnceScope as Mt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as N, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Nn, CLIError as Nt, COMMANDCODE_DIR as O, CURATED_RULES_FEATURE_SUBDIR as On, fallbackLogger as Ot, caseFoldIdentity as P, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Pn, ErrorCodes as Pt, RulesyncRuleFrontmatterSchema as Q, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Qn, isFileSystemError as Qt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as R, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_DIR as S, ALL_TOOL_TARGETS_WITH_WILDCARD as Sn, assertTargetsFeaturesExclusive as St, LETTACODE_SETTINGS_LOCAL_FILE_NAME as T, ALL_FEATURES as Tn, ConsoleLogger as Tt, CLAUDECODE_SKILLS_DIR_PATH as U, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_MEMORIES_DIR_NAME as V, RULESYNC_MCP_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, findProjectInstructionBudgetFindings as W, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Wn, directoryExists as Wt, RulesyncSkillFrontmatterSchema as X, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkill as Y, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRule as Z, RULESYNC_USER_CONFIG_DIR_NAME as Zn, isFileNotFoundError as Zt, SubagentsProcessor as _, toPosixPath as _n, resolveEffectiveInputRoots as _t, convertFromTool as a, readFileContent as an, stripControlCharactersKeepingLineFeeds as ar, parseJsonc as at, CommandsProcessor as b, mapWithConcurrency as bn, GITIGNORE_DESTINATION_KEY as bt, parseCommaSeparatedList as c, removeDirectoryStrict as cn, RulesyncCheck as ct, shortenToWidth as d, removeFileWithoutFollowingSymlinks as dn, loadYaml as dt, listDirectoryEntryNames as en, truncateText as er, RulesyncMcp as et, McpProcessor as f, removeTempDirectory as fn, isRecord as ft, RulesProcessor as g, splitPathSegments as gn, mergeInputRootConfigs as gt, ChecksProcessor as h, runWithDirectoryRollback as hn, ConfigResolver as ht, inspectInputRoots as i, posixRelativePathEscapesRoot as in, stripControlCharacters as ir, resolveRulesyncSourceWritePath as it, CODEBUDDY_LOCAL_RULE_FILE_NAME as j, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as jn, resetRunWarningState as jt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as k, FEATURE_SOURCE_TREE_ENTRIES as kn, warnOnConflictingFlags as kt, ELLIPSIS_WIDTH as l, removeFile as ln, RulesyncCheckFrontmatterSchema as lt, HooksProcessor as m, resolvedRelativePath as mn, SKILL_FILE_NAME as mt, formatSourceLoadFailure as n, listSubdirectoryNames as nn, hasEnclosingMarkOutsideKeycap as nr, RulesyncHooks as nt, isPackagingToolTarget as o, readFileContentOrNull as on, stripControlCharactersKeepingLineFeedsAndTabs as or, RulesyncCommand as ot, IgnoreProcessor as p, resolvePath as pn, SHARED_USER_MANAGED_CONFIG_PATHS as pt, RulesyncSubagent as q, RULESYNC_RULES_RELATIVE_DIR_PATH as qn, fileExistsStrict as qt, generate as r, pathEscapesRoot as rn, quoteForLog as rr, getRulesyncSourceCandidates as rt, getProcessorRegistryEntry as s, removeDirectory as sn, stripHiddenCharacters as sr, RulesyncCommandFrontmatterSchema as st, importFromTool as t, listFilePathsRecursively as tn, hasDeceptiveHiddenCharacters as tr, RulesyncIgnore as tt, displayWidthOf as u, removeFileStrict as un, stringifyFrontmatter as ut, SkillsProcessor as v, writeFileBuffer as vn, CONFLICTING_TARGET_PAIRS as vt, LETTACODE_DIR as w, ToolTargetSchema as wn, findControlCharacter as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, ALL_TOOL_TARGETS as xn, SourceEntrySchema as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, writeFileContent as yn, ConfigFileSchema as yt, CLAUDECODE_DIR as z, RULESYNC_IGNORE_RELATIVE_FILE_PATH as zn, assertPathResolvesInsideRoot as zt };
106434
106477
 
106435
- //# sourceMappingURL=import-DdmJUW78.js.map
106478
+ //# sourceMappingURL=import-D2uYjAdc.js.map