rulesync 43.1.1 → 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, collapseWhitespaceForLength } 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,9 +55868,10 @@ 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
  }
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.`);
55864
55872
  if (maxLength !== void 0 && typeof value === "string") {
55865
55873
  const length = collapseWhitespaceForLength ? value.replace(/\s+/g, " ").trim().length : value.length;
55866
- if (length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${length} characters long; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
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.`);
55867
55875
  }
55868
55876
  }
55869
55877
  return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
@@ -64264,14 +64272,16 @@ const QODER_SPEC = {
64264
64272
  {
64265
64273
  canonical: "rewakeMessage",
64266
64274
  tool: "rewakeMessage",
64267
- commandOnly: true
64275
+ commandOnly: true,
64276
+ onlyWithFlag: "asyncRewake"
64268
64277
  },
64269
64278
  {
64270
64279
  canonical: "rewakeSummary",
64271
64280
  tool: "rewakeSummary",
64272
64281
  commandOnly: true,
64273
64282
  maxLength: 300,
64274
- collapseWhitespaceForLength: true
64283
+ collapseWhitespaceForLength: true,
64284
+ onlyWithFlag: "asyncRewake"
64275
64285
  }
64276
64286
  ],
64277
64287
  booleanPassthroughFields: [
@@ -69651,7 +69661,7 @@ var CodebuddyMcp = class CodebuddyMcp extends ToolMcp {
69651
69661
  }
69652
69662
  };
69653
69663
  // src/features/mcp/codebuff-mcp.ts
69654
- const TOOL_NAME$1 = "codebuff";
69664
+ const TOOL_NAME$2 = "codebuff";
69655
69665
  const MAPPED_KEYS = /* @__PURE__ */ new Set([
69656
69666
  "type",
69657
69667
  "transport",
@@ -69686,14 +69696,14 @@ function toCodebuffEnv({ name, env, logger }) {
69686
69696
  }
69687
69697
  if (value.startsWith("$")) {
69688
69698
  warnAndSkipMcpServer({
69689
- toolName: TOOL_NAME$1,
69699
+ toolName: TOOL_NAME$2,
69690
69700
  serverName: name,
69691
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`,
69692
69702
  logger
69693
69703
  });
69694
69704
  return null;
69695
69705
  }
69696
- 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.`);
69697
69707
  converted.push([key, value]);
69698
69708
  }
69699
69709
  return Object.fromEntries(converted);
@@ -69710,31 +69720,31 @@ function toCodebuffEnv({ name, env, logger }) {
69710
69720
  */
69711
69721
  function toCodebuffServer({ name, server, logger }) {
69712
69722
  if (server.disabled === true) return warnAndSkipMcpServer({
69713
- toolName: TOOL_NAME$1,
69723
+ toolName: TOOL_NAME$2,
69714
69724
  serverName: name,
69715
69725
  reason: "disabled: true, which Codebuff has no per-server switch for",
69716
69726
  logger
69717
69727
  });
69718
69728
  const transport = server.type ?? server.transport;
69719
69729
  if (transport === "ws") return warnAndSkipMcpServer({
69720
- toolName: TOOL_NAME$1,
69730
+ toolName: TOOL_NAME$2,
69721
69731
  serverName: name,
69722
69732
  reason: "the WebSocket transport, which Codebuff does not support",
69723
69733
  logger
69724
69734
  });
69725
69735
  const dropped = Object.keys(server).filter((key) => !MAPPED_KEYS.has(key));
69726
- 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.`);
69727
69737
  if (isRemoteMcpServer(server)) {
69728
69738
  const url = resolveRemoteMcpUrl(server);
69729
69739
  if (url === void 0) return warnAndSkipMcpServer({
69730
- toolName: TOOL_NAME$1,
69740
+ toolName: TOOL_NAME$2,
69731
69741
  serverName: name,
69732
69742
  reason: "a remote transport without a url",
69733
69743
  logger
69734
69744
  });
69735
69745
  const params = server.params;
69736
69746
  const validParams = isRecord(params) && Object.values(params).every((value) => typeof value === "string");
69737
- 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.`);
69738
69748
  return {
69739
69749
  type: transport === "sse" ? "sse" : "http",
69740
69750
  url,
@@ -69744,7 +69754,7 @@ function toCodebuffServer({ name, server, logger }) {
69744
69754
  }
69745
69755
  const [command, ...args] = resolveLocalMcpCommand(server);
69746
69756
  if (command === void 0) return warnAndSkipMcpServer({
69747
- toolName: TOOL_NAME$1,
69757
+ toolName: TOOL_NAME$2,
69748
69758
  serverName: name,
69749
69759
  reason: "no command to spawn",
69750
69760
  logger
@@ -72414,7 +72424,7 @@ var CursorMcp = class CursorMcp extends ToolMcp {
72414
72424
  }
72415
72425
  };
72416
72426
  // src/features/mcp/deepagents-mcp.ts
72417
- const TOOL_NAME = "deepagents";
72427
+ const TOOL_NAME$1 = "deepagents";
72418
72428
  /**
72419
72429
  * Map a canonical transport onto the three dcode accepts.
72420
72430
  *
@@ -72455,7 +72465,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72455
72465
  return true;
72456
72466
  }
72457
72467
  warnAndSkipMcpServer({
72458
- toolName: TOOL_NAME,
72468
+ toolName: TOOL_NAME$1,
72459
72469
  serverName: name,
72460
72470
  reason: "an empty command list, which leaves deepagents nothing to spawn",
72461
72471
  logger
@@ -72483,7 +72493,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72483
72493
  */
72484
72494
  function toDeepagentsOauth({ name, oauth, converted, logger }) {
72485
72495
  if (!isSlackOauthServer(converted)) {
72486
- 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.`);
72487
72497
  return;
72488
72498
  }
72489
72499
  const source = isRecord(oauth) ? oauth : {};
@@ -72492,18 +72502,18 @@ function toDeepagentsOauth({ name, oauth, converted, logger }) {
72492
72502
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
72493
72503
  const nativeKey = key === "clientId" ? "client_id" : key;
72494
72504
  if (nativeKey !== "client_id" && nativeKey !== "client_secret") {
72495
- 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.`);
72496
72506
  continue;
72497
72507
  }
72498
72508
  if (nativeKey !== key && isDeepagentsOauthValue(source[nativeKey])) continue;
72499
72509
  if (!isDeepagentsOauthValue(value)) {
72500
- 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.`);
72501
72511
  continue;
72502
72512
  }
72503
72513
  result[nativeKey] = value;
72504
72514
  }
72505
72515
  if (result.client_id === void 0) {
72506
- 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.`);
72507
72517
  return;
72508
72518
  }
72509
72519
  return result;
@@ -72532,14 +72542,14 @@ function isSlackOauthServer(server) {
72532
72542
  */
72533
72543
  function toDeepagentsServer({ name, server, logger }) {
72534
72544
  if (server.disabled === true) return warnAndSkipMcpServer({
72535
- toolName: TOOL_NAME,
72545
+ toolName: TOOL_NAME$1,
72536
72546
  serverName: name,
72537
72547
  reason: "disabled: true, which deepagents does not read from .mcp.json (the server would still run)",
72538
72548
  logger
72539
72549
  });
72540
72550
  const rawTransport = server.transport ?? server.type;
72541
72551
  if (rawTransport === "ws") return warnAndSkipMcpServer({
72542
- toolName: TOOL_NAME,
72552
+ toolName: TOOL_NAME$1,
72543
72553
  serverName: name,
72544
72554
  reason: "the WebSocket transport, which deepagents does not support",
72545
72555
  logger
@@ -72569,21 +72579,21 @@ function toDeepagentsServer({ name, server, logger }) {
72569
72579
  if (deepagentsOauth !== void 0) converted.oauth = deepagentsOauth;
72570
72580
  }
72571
72581
  if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
72572
- toolName: TOOL_NAME,
72582
+ toolName: TOOL_NAME$1,
72573
72583
  serverName: name,
72574
72584
  reason: "both enabledTools and disabledTools, which deepagents rejects — pick one",
72575
72585
  logger
72576
72586
  });
72577
72587
  if (enabledTools !== void 0) {
72578
72588
  if (enabledTools.length === 0) return warnAndSkipMcpServer({
72579
- toolName: TOOL_NAME,
72589
+ toolName: TOOL_NAME$1,
72580
72590
  serverName: name,
72581
72591
  reason: "an empty enabledTools list, which allows no tools at all and which deepagents rejects",
72582
72592
  logger
72583
72593
  });
72584
72594
  converted.allowedTools = enabledTools;
72585
72595
  } else if (disabledTools !== void 0) {
72586
- 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.`);
72587
72597
  else converted.disabledTools = disabledTools;
72588
72598
  }
72589
72599
  return converted;
@@ -73235,6 +73245,32 @@ var DshMcp = class DshMcp extends ToolMcp {
73235
73245
  }
73236
73246
  };
73237
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
+ }
73238
73274
  var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73239
73275
  json;
73240
73276
  constructor(params) {
@@ -73260,8 +73296,11 @@ var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73260
73296
  validate
73261
73297
  });
73262
73298
  }
73263
- static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
73264
- 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
+ }) };
73265
73304
  const fileContent = JSON.stringify(factorydroidConfig, null, 2);
73266
73305
  return new FactorydroidMcp({
73267
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, collapseWhitespaceForLength } 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,9 +55846,10 @@ 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
  }
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.`);
55842
55850
  if (maxLength !== void 0 && typeof value === "string") {
55843
55851
  const length = collapseWhitespaceForLength ? value.replace(/\s+/g, " ").trim().length : value.length;
55844
- if (length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${length} characters long; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
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.`);
55845
55853
  }
55846
55854
  }
55847
55855
  return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
@@ -64242,14 +64250,16 @@ const QODER_SPEC = {
64242
64250
  {
64243
64251
  canonical: "rewakeMessage",
64244
64252
  tool: "rewakeMessage",
64245
- commandOnly: true
64253
+ commandOnly: true,
64254
+ onlyWithFlag: "asyncRewake"
64246
64255
  },
64247
64256
  {
64248
64257
  canonical: "rewakeSummary",
64249
64258
  tool: "rewakeSummary",
64250
64259
  commandOnly: true,
64251
64260
  maxLength: 300,
64252
- collapseWhitespaceForLength: true
64261
+ collapseWhitespaceForLength: true,
64262
+ onlyWithFlag: "asyncRewake"
64253
64263
  }
64254
64264
  ],
64255
64265
  booleanPassthroughFields: [
@@ -69629,7 +69639,7 @@ var CodebuddyMcp = class CodebuddyMcp extends ToolMcp {
69629
69639
  }
69630
69640
  };
69631
69641
  // src/features/mcp/codebuff-mcp.ts
69632
- const TOOL_NAME$1 = "codebuff";
69642
+ const TOOL_NAME$2 = "codebuff";
69633
69643
  const MAPPED_KEYS = /* @__PURE__ */ new Set([
69634
69644
  "type",
69635
69645
  "transport",
@@ -69664,14 +69674,14 @@ function toCodebuffEnv({ name, env, logger }) {
69664
69674
  }
69665
69675
  if (value.startsWith("$")) {
69666
69676
  warnAndSkipMcpServer({
69667
- toolName: TOOL_NAME$1,
69677
+ toolName: TOOL_NAME$2,
69668
69678
  serverName: name,
69669
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`,
69670
69680
  logger
69671
69681
  });
69672
69682
  return null;
69673
69683
  }
69674
- 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.`);
69675
69685
  converted.push([key, value]);
69676
69686
  }
69677
69687
  return Object.fromEntries(converted);
@@ -69688,31 +69698,31 @@ function toCodebuffEnv({ name, env, logger }) {
69688
69698
  */
69689
69699
  function toCodebuffServer({ name, server, logger }) {
69690
69700
  if (server.disabled === true) return warnAndSkipMcpServer({
69691
- toolName: TOOL_NAME$1,
69701
+ toolName: TOOL_NAME$2,
69692
69702
  serverName: name,
69693
69703
  reason: "disabled: true, which Codebuff has no per-server switch for",
69694
69704
  logger
69695
69705
  });
69696
69706
  const transport = server.type ?? server.transport;
69697
69707
  if (transport === "ws") return warnAndSkipMcpServer({
69698
- toolName: TOOL_NAME$1,
69708
+ toolName: TOOL_NAME$2,
69699
69709
  serverName: name,
69700
69710
  reason: "the WebSocket transport, which Codebuff does not support",
69701
69711
  logger
69702
69712
  });
69703
69713
  const dropped = Object.keys(server).filter((key) => !MAPPED_KEYS.has(key));
69704
- 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.`);
69705
69715
  if (isRemoteMcpServer(server)) {
69706
69716
  const url = resolveRemoteMcpUrl(server);
69707
69717
  if (url === void 0) return warnAndSkipMcpServer({
69708
- toolName: TOOL_NAME$1,
69718
+ toolName: TOOL_NAME$2,
69709
69719
  serverName: name,
69710
69720
  reason: "a remote transport without a url",
69711
69721
  logger
69712
69722
  });
69713
69723
  const params = server.params;
69714
69724
  const validParams = isRecord(params) && Object.values(params).every((value) => typeof value === "string");
69715
- 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.`);
69716
69726
  return {
69717
69727
  type: transport === "sse" ? "sse" : "http",
69718
69728
  url,
@@ -69722,7 +69732,7 @@ function toCodebuffServer({ name, server, logger }) {
69722
69732
  }
69723
69733
  const [command, ...args] = resolveLocalMcpCommand(server);
69724
69734
  if (command === void 0) return warnAndSkipMcpServer({
69725
- toolName: TOOL_NAME$1,
69735
+ toolName: TOOL_NAME$2,
69726
69736
  serverName: name,
69727
69737
  reason: "no command to spawn",
69728
69738
  logger
@@ -72392,7 +72402,7 @@ var CursorMcp = class CursorMcp extends ToolMcp {
72392
72402
  }
72393
72403
  };
72394
72404
  // src/features/mcp/deepagents-mcp.ts
72395
- const TOOL_NAME = "deepagents";
72405
+ const TOOL_NAME$1 = "deepagents";
72396
72406
  /**
72397
72407
  * Map a canonical transport onto the three dcode accepts.
72398
72408
  *
@@ -72433,7 +72443,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72433
72443
  return true;
72434
72444
  }
72435
72445
  warnAndSkipMcpServer({
72436
- toolName: TOOL_NAME,
72446
+ toolName: TOOL_NAME$1,
72437
72447
  serverName: name,
72438
72448
  reason: "an empty command list, which leaves deepagents nothing to spawn",
72439
72449
  logger
@@ -72461,7 +72471,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
72461
72471
  */
72462
72472
  function toDeepagentsOauth({ name, oauth, converted, logger }) {
72463
72473
  if (!isSlackOauthServer(converted)) {
72464
- 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.`);
72465
72475
  return;
72466
72476
  }
72467
72477
  const source = isRecord(oauth) ? oauth : {};
@@ -72470,18 +72480,18 @@ function toDeepagentsOauth({ name, oauth, converted, logger }) {
72470
72480
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
72471
72481
  const nativeKey = key === "clientId" ? "client_id" : key;
72472
72482
  if (nativeKey !== "client_id" && nativeKey !== "client_secret") {
72473
- 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.`);
72474
72484
  continue;
72475
72485
  }
72476
72486
  if (nativeKey !== key && isDeepagentsOauthValue(source[nativeKey])) continue;
72477
72487
  if (!isDeepagentsOauthValue(value)) {
72478
- 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.`);
72479
72489
  continue;
72480
72490
  }
72481
72491
  result[nativeKey] = value;
72482
72492
  }
72483
72493
  if (result.client_id === void 0) {
72484
- 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.`);
72485
72495
  return;
72486
72496
  }
72487
72497
  return result;
@@ -72510,14 +72520,14 @@ function isSlackOauthServer(server) {
72510
72520
  */
72511
72521
  function toDeepagentsServer({ name, server, logger }) {
72512
72522
  if (server.disabled === true) return warnAndSkipMcpServer({
72513
- toolName: TOOL_NAME,
72523
+ toolName: TOOL_NAME$1,
72514
72524
  serverName: name,
72515
72525
  reason: "disabled: true, which deepagents does not read from .mcp.json (the server would still run)",
72516
72526
  logger
72517
72527
  });
72518
72528
  const rawTransport = server.transport ?? server.type;
72519
72529
  if (rawTransport === "ws") return warnAndSkipMcpServer({
72520
- toolName: TOOL_NAME,
72530
+ toolName: TOOL_NAME$1,
72521
72531
  serverName: name,
72522
72532
  reason: "the WebSocket transport, which deepagents does not support",
72523
72533
  logger
@@ -72547,21 +72557,21 @@ function toDeepagentsServer({ name, server, logger }) {
72547
72557
  if (deepagentsOauth !== void 0) converted.oauth = deepagentsOauth;
72548
72558
  }
72549
72559
  if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
72550
- toolName: TOOL_NAME,
72560
+ toolName: TOOL_NAME$1,
72551
72561
  serverName: name,
72552
72562
  reason: "both enabledTools and disabledTools, which deepagents rejects — pick one",
72553
72563
  logger
72554
72564
  });
72555
72565
  if (enabledTools !== void 0) {
72556
72566
  if (enabledTools.length === 0) return warnAndSkipMcpServer({
72557
- toolName: TOOL_NAME,
72567
+ toolName: TOOL_NAME$1,
72558
72568
  serverName: name,
72559
72569
  reason: "an empty enabledTools list, which allows no tools at all and which deepagents rejects",
72560
72570
  logger
72561
72571
  });
72562
72572
  converted.allowedTools = enabledTools;
72563
72573
  } else if (disabledTools !== void 0) {
72564
- 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.`);
72565
72575
  else converted.disabledTools = disabledTools;
72566
72576
  }
72567
72577
  return converted;
@@ -73213,6 +73223,32 @@ var DshMcp = class DshMcp extends ToolMcp {
73213
73223
  }
73214
73224
  };
73215
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
+ }
73216
73252
  var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73217
73253
  json;
73218
73254
  constructor(params) {
@@ -73238,8 +73274,11 @@ var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
73238
73274
  validate
73239
73275
  });
73240
73276
  }
73241
- static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
73242
- 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
+ }) };
73243
73282
  const fileContent = JSON.stringify(factorydroidConfig, null, 2);
73244
73283
  return new FactorydroidMcp({
73245
73284
  outputRoot,
@@ -106436,4 +106475,4 @@ async function importChecksCore(params) {
106436
106475
  }
106437
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 };
106438
106477
 
106439
- //# sourceMappingURL=import-COu_2AUF.js.map
106478
+ //# sourceMappingURL=import-D2uYjAdc.js.map