rulesync 40.1.0 → 40.2.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.
@@ -23474,6 +23474,62 @@ function findOpencodeV2McpContainers(mcp) {
23474
23474
  return [...envelope !== null && Object.keys(envelope).length > 0 ? ["mcp.servers"] : [], ...isOpencodeV2GlobalTimeout(mcp.timeout) && Object.keys(mcp.timeout).length > 0 ? ["mcp.timeout"] : []];
23475
23475
  }
23476
23476
  /**
23477
+ * Returns the non-empty V2 global `mcp.timeout` object of an OpenCode `mcp`
23478
+ * value, or `undefined` when there is none.
23479
+ */
23480
+ function getOpencodeV2GlobalMcpTimeout(mcp) {
23481
+ return isRecord$1(mcp) && findOpencodeV2McpContainers(mcp).includes("mcp.timeout") ? mcp.timeout : void 0;
23482
+ }
23483
+ /**
23484
+ * Returns the raw entry an OpenCode `mcp` value holds for a server: the one
23485
+ * under the V2 `mcp.servers` envelope, or else the flat V1 entry.
23486
+ */
23487
+ function getOpencodeMcpServerEntry(mcp, name) {
23488
+ if (!isRecord$1(mcp)) return;
23489
+ const envelope = getOpencodeV2McpEnvelope(mcp);
23490
+ if (envelope !== null && Object.hasOwn(envelope, name)) return envelope[name];
23491
+ return Object.hasOwn(mcp, name) ? mcp[name] : void 0;
23492
+ }
23493
+ const OPENCODE_V1_TO_V2_OAUTH_KEYS = new Map([...OPENCODE_V2_OAUTH_KEYS].map(([v2, v1]) => [v1, v2]));
23494
+ /**
23495
+ * Raises one V1-shaped OpenCode MCP server (`local` or `remote`) to the V2
23496
+ * spelling — the inverse of {@link lowerOpencodeV2Server}:
23497
+ *
23498
+ * - `enabled` is dropped and `disabled: true` written only for a disabled server;
23499
+ * - a number `timeout` becomes `{ catalog, execution }`, and camelCase `oauth`
23500
+ * keys become snake_case.
23501
+ *
23502
+ * Values Rulesync's canonical form cannot hold are taken from the entry the
23503
+ * file already has for the server (`existing`): its `timeout` object and its
23504
+ * `oauth` object are kept verbatim whenever lowering them gives back exactly
23505
+ * what the canonical server carries, so a V2 file survives import and
23506
+ * generate unchanged. When the canonical `timeout` differs, the existing
23507
+ * `startup` value is still kept.
23508
+ */
23509
+ function raiseOpencodeServerToV2(server, existing) {
23510
+ const { enabled, timeout, oauth, ...rest } = server;
23511
+ const raised = { ...rest };
23512
+ if (enabled === false) raised.disabled = true;
23513
+ const existingTimeout = isRecord$1(existing) && isRecord$1(existing.timeout) ? existing.timeout : null;
23514
+ if (existingTimeout !== null && lowerOpencodeV2Timeout(existingTimeout) === timeout) raised.timeout = existingTimeout;
23515
+ else if (typeof timeout === "number") {
23516
+ const startup = existingTimeout?.startup;
23517
+ raised.timeout = {
23518
+ ...startup !== void 0 && { startup },
23519
+ catalog: timeout,
23520
+ execution: timeout
23521
+ };
23522
+ } else if (timeout !== void 0) raised.timeout = timeout;
23523
+ const existingOauth = isRecord$1(existing) && isRecord$1(existing.oauth) ? existing.oauth : null;
23524
+ if (existingOauth !== null && isRecord$1(oauth) && isDeepStrictEqual(lowerOpencodeV2OAuth(existingOauth, ""), oauth)) raised.oauth = existingOauth;
23525
+ else if (isRecord$1(oauth)) {
23526
+ const snakeOauth = {};
23527
+ for (const [key, value] of Object.entries(oauth)) setOwnEntry(snakeOauth, OPENCODE_V1_TO_V2_OAUTH_KEYS.get(key) ?? key, value);
23528
+ raised.oauth = snakeOauth;
23529
+ } else if (oauth !== void 0) raised.oauth = oauth;
23530
+ return raised;
23531
+ }
23532
+ /**
23477
23533
  * Lowers OpenCode V2 `mcp` spellings found in a V1 config to the flat V1 map,
23478
23534
  * the way OpenCode V1 itself does since v1.18.24 (`normalizeMcp`):
23479
23535
  *
@@ -33935,6 +33991,69 @@ var JunieHooks = class JunieHooks extends ToolHooks {
33935
33991
  }
33936
33992
  };
33937
33993
  //#endregion
33994
+ //#region src/features/hooks/hook-block-reason.ts
33995
+ /**
33996
+ * Source lines of a `sanitizeBlockReason(raw)` helper, emitted into generated
33997
+ * hook plugins that hand a hook command's output to the agent as the reason a
33998
+ * call was blocked (the Pi / oh-my-pi extension and the OpenCode-style plugin).
33999
+ * It returns the sanitized, length-capped text, or `""` when nothing survives
34000
+ * so the caller can substitute its own fallback.
34001
+ *
34002
+ * The text is sanitized because a hook command's output can relay third-party
34003
+ * content (linter output, matched file lines) into a terminal or, in RPC mode,
34004
+ * into an external client. Escape sequences, C1 and other control characters,
34005
+ * and format characters such as bidirectional overrides and zero-width joiners
34006
+ * are stripped so the reason cannot repaint or reorder what the user is shown,
34007
+ * and carriage returns are folded into newlines so it cannot overwrite an
34008
+ * already printed line. The format-character pass is a superset of the bidi
34009
+ * controls listed in `CONTROL_CHARACTERS_PATTERN`
34010
+ * (`src/utils/control-characters.ts`): a blocked-hook reason is short
34011
+ * diagnostic text, so dropping every `Cf` character is safer than enumerating
34012
+ * the harmful ones and missing `U+061C`, `U+FEFF`, or a future addition.
34013
+ *
34014
+ * Sanitization runs before truncation so a reason that begins with a progress
34015
+ * banner is still reported by its content. The raw text is only sliced to a
34016
+ * generous backstop first: a hook command may emit a large amount of output,
34017
+ * and the slice bounds the work even if a later edit reintroduces a pattern
34018
+ * that is not linear. A slice that actually fired is reported as truncated, so
34019
+ * the text is never silently cut.
34020
+ *
34021
+ * Both slices cut on UTF-16 code units, so each drops a trailing lone high
34022
+ * surrogate: the reason may travel as JSON, and half a surrogate pair is not
34023
+ * text a strict consumer has to accept.
34024
+ *
34025
+ * `typed` emits TypeScript parameter and return annotations (for the Pi
34026
+ * extension, a `.ts` module); without it the lines are plain JavaScript.
34027
+ */
34028
+ function buildBlockReasonSanitizerLines({ typed }) {
34029
+ const str = typed ? ": string" : "";
34030
+ return [
34031
+ "const MAX_BLOCK_REASON_LENGTH = 2000;",
34032
+ "const MAX_SCANNED_REASON_LENGTH = 128_000;",
34033
+ "",
34034
+ `function dropTrailingLoneSurrogate(text${str})${str} {`,
34035
+ " return text.replace(/[\\ud800-\\udbff]$/, \"\");",
34036
+ "}",
34037
+ "",
34038
+ `function sanitizeBlockReason(raw${str})${str} {`,
34039
+ " const scannedTruncated = raw.length > MAX_SCANNED_REASON_LENGTH;",
34040
+ " const scanned = scannedTruncated",
34041
+ " ? dropTrailingLoneSurrogate(raw.slice(0, MAX_SCANNED_REASON_LENGTH))",
34042
+ " : raw;",
34043
+ " const sanitized = scanned",
34044
+ " .replace(/\\r\\n?/g, \"\\n\")",
34045
+ " .replace(/\\u001b\\[[0-9;?]*[\\u0020-\\u002f]*[\\u0040-\\u007e]/g, \"\")",
34046
+ " .replace(/\\u001b\\][^\\u0007\\u001b]{0,256}(?:\\u0007|\\u001b\\\\)/g, \"\")",
34047
+ " .replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f-\\u009f]/g, \"\")",
34048
+ " .replace(/[\\p{Cf}\\p{Zl}\\p{Zp}]/gu, \"\")",
34049
+ " .trim();",
34050
+ " if (!sanitized) return \"\";",
34051
+ " if (sanitized.length <= MAX_BLOCK_REASON_LENGTH && !scannedTruncated) return sanitized;",
34052
+ " return `${dropTrailingLoneSurrogate(sanitized.slice(0, MAX_BLOCK_REASON_LENGTH))}...`;",
34053
+ "}"
34054
+ ];
34055
+ }
34056
+ //#endregion
33938
34057
  //#region src/features/hooks/opencode-style-generator.ts
33939
34058
  /**
33940
34059
  * Tool events emitted as named `(input, ...)` hooks rather than through the
@@ -34005,6 +34124,87 @@ const SHELL_EVENT_TOOL_GATES = {
34005
34124
  tool: "bash"
34006
34125
  }
34007
34126
  };
34127
+ /**
34128
+ * Canonical events whose command receives a Claude Code-style JSON payload on
34129
+ * stdin and whose result can deny the call, mapped to the `hook_event_name`
34130
+ * the payload carries. Only `preToolUse` (`tool.execute.before`) takes part:
34131
+ * it is the one named hook where throwing aborts the call — OpenCode rejects
34132
+ * the tool call and returns the error message to the model (the `.env`
34133
+ * protection example in the plugins doc).
34134
+ *
34135
+ * The payload mirrors Claude Code's `PreToolUse` input: `hook_event_name`,
34136
+ * `session_id` (`input.sessionID`), `tool_use_id` (`input.callID`),
34137
+ * `tool_name` (the raw OpenCode tool name — `bash`, `edit`, `<server>_<tool>`
34138
+ * for MCP — the same subject a `matcher` is tested against), `tool_input`
34139
+ * (`output.args`) and `cwd` (the plugin context's `directory`, falling back to
34140
+ * `process.cwd()`). It is serialized lazily, on the first handler whose
34141
+ * matcher matches, so a tool call no hook applies to pays nothing for it.
34142
+ *
34143
+ * Bun Shell has no stdin option on a command and rejects redirections on a
34144
+ * subshell, so the payload is piped in by the shell's builtin `echo`
34145
+ * (`echo ${payload} | <command>`); Bun escapes the interpolated value, so the
34146
+ * payload never reaches the shell as syntax. The pipe feeds the first
34147
+ * pipeline of the command — a compound `a && b` or `a; b` gives the payload
34148
+ * to `a` only, so multi-step guards belong in a script.
34149
+ *
34150
+ * @see https://opencode.ai/docs/plugins/
34151
+ * @see https://bun.sh/docs/runtime/shell
34152
+ * @see https://code.claude.com/docs/en/hooks
34153
+ */
34154
+ const STDIN_PAYLOAD_EVENTS = { preToolUse: "PreToolUse" };
34155
+ /**
34156
+ * Name of the helper the generated plugin defines when a `preToolUse` handler
34157
+ * is present. It interprets the command's result like Claude Code does for a
34158
+ * `PreToolUse` hook, throwing (which aborts the OpenCode tool call) when the
34159
+ * hook denies it:
34160
+ * - exit code 2 → deny, with the command's stderr as the reason;
34161
+ * - exit code 0 with JSON stdout whose `hookSpecificOutput.permissionDecision`
34162
+ * is `"deny"` → deny, with `permissionDecisionReason` as the reason;
34163
+ * - exit code 0 otherwise → allow;
34164
+ * - any other non-zero exit → deny too, with stderr or the exit code.
34165
+ *
34166
+ * Every reason goes through the `sanitizeBlockReason` helper shared with the
34167
+ * Pi extension (`hook-block-reason.ts`): control and format characters are
34168
+ * stripped and the text is capped at 2000 characters before the model sees it.
34169
+ *
34170
+ * The other non-zero exit case is deliberate. Claude Code treats that as a
34171
+ * non-blocking error, but before this payload contract existed the plugin
34172
+ * ran the command through Bun Shell's default throwing mode, so every
34173
+ * non-zero exit aborted the call. Keeping that preserves guards that block
34174
+ * with `exit 1`, rather than silently failing them open.
34175
+ */
34176
+ const PRE_TOOL_USE_RESULT_HELPER = "__rulesyncEnforcePreToolUseResult";
34177
+ const PRE_TOOL_USE_RESULT_HELPER_LINES = [
34178
+ ...buildBlockReasonSanitizerLines({ typed: false }),
34179
+ "",
34180
+ `const ${PRE_TOOL_USE_RESULT_HELPER} = (result) => {`,
34181
+ " const stderr = result.stderr.toString();",
34182
+ " if (result.exitCode === 0) {",
34183
+ " let parsed;",
34184
+ " try {",
34185
+ " parsed = JSON.parse(result.stdout.toString());",
34186
+ " } catch {",
34187
+ " return;",
34188
+ " }",
34189
+ " const specific = parsed && typeof parsed === \"object\" ? parsed.hookSpecificOutput : undefined;",
34190
+ " if (specific && typeof specific === \"object\" && specific.permissionDecision === \"deny\") {",
34191
+ " throw new Error(",
34192
+ " (typeof specific.permissionDecisionReason === \"string\" &&",
34193
+ " sanitizeBlockReason(specific.permissionDecisionReason)) ||",
34194
+ " \"Blocked by a preToolUse hook.\",",
34195
+ " );",
34196
+ " }",
34197
+ " return;",
34198
+ " }",
34199
+ " if (result.exitCode === 2) {",
34200
+ " throw new Error(sanitizeBlockReason(stderr) || \"Blocked by a preToolUse hook.\");",
34201
+ " }",
34202
+ " throw new Error(",
34203
+ " sanitizeBlockReason(stderr) ||",
34204
+ " \"preToolUse hook command failed with exit code \" + result.exitCode + \".\",",
34205
+ " );",
34206
+ "};"
34207
+ ];
34008
34208
  function escapeForTemplateLiteral(command) {
34009
34209
  return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
34010
34210
  }
@@ -34019,6 +34219,17 @@ function validateAndSanitizeMatcher(matcher) {
34019
34219
  }
34020
34220
  return sanitized.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
34021
34221
  }
34222
+ /** The per-canonical-event gates and payload flag every handler of it carries. */
34223
+ function handlerExtrasFor(canonicalEvent) {
34224
+ const shellGate = SHELL_EVENT_TOOL_GATES[canonicalEvent];
34225
+ const propertyGate = GENERIC_EVENT_PROPERTY_GATES[canonicalEvent];
34226
+ const stdinEvent = shellGate ? void 0 : STDIN_PAYLOAD_EVENTS[canonicalEvent];
34227
+ return {
34228
+ ...shellGate ? { toolGate: shellGate.tool } : {},
34229
+ ...propertyGate ? { propertyGate } : {},
34230
+ ...stdinEvent ? { stdinEvent } : {}
34231
+ };
34232
+ }
34022
34233
  /**
34023
34234
  * Group the effective hooks into named (tool.execute.*) and generic event
34024
34235
  * handler groups, keyed by tool event name. Mutates the supplied groups.
@@ -34026,7 +34237,7 @@ function validateAndSanitizeMatcher(matcher) {
34026
34237
  function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHandlers, genericEventHandlers }) {
34027
34238
  for (const [canonicalEvent, definitions] of Object.entries(effectiveHooks)) {
34028
34239
  const shellGate = SHELL_EVENT_TOOL_GATES[canonicalEvent];
34029
- const propertyGate = GENERIC_EVENT_PROPERTY_GATES[canonicalEvent];
34240
+ const extras = handlerExtrasFor(canonicalEvent);
34030
34241
  const toolEvent = shellGate?.toolEvent ?? eventMap[canonicalEvent];
34031
34242
  if (!toolEvent) continue;
34032
34243
  const matcherSupported = !shellGate && Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) && NAMED_HOOK_MATCHER_SUBJECTS[toolEvent] !== null;
@@ -34038,8 +34249,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
34038
34249
  handlers.push({
34039
34250
  command: def.command,
34040
34251
  matcher: def.matcher ? def.matcher : void 0,
34041
- ...shellGate ? { toolGate: shellGate.tool } : {},
34042
- ...propertyGate ? { propertyGate } : {}
34252
+ ...extras
34043
34253
  });
34044
34254
  }
34045
34255
  if (handlers.length > 0) {
@@ -34072,46 +34282,79 @@ function buildGenericEventBodyLines(genericEventHandlers) {
34072
34282
  bodyLines.push(" },");
34073
34283
  return bodyLines;
34074
34284
  }
34285
+ function stdinPayloadVariable(stdinEvent) {
34286
+ return `__rulesync${stdinEvent}Input`;
34287
+ }
34288
+ /**
34289
+ * The statement that runs a handler's command. A handler with a stdin payload
34290
+ * pipes the payload in and hands the result to the deny-aware helper; every
34291
+ * other handler keeps Bun Shell's default mode, where a non-zero exit throws.
34292
+ */
34293
+ function buildRunStatement(handler) {
34294
+ const escapedCommand = escapeForTemplateLiteral(handler.command);
34295
+ if (handler.stdinEvent) {
34296
+ const payload = stdinPayloadVariable(handler.stdinEvent);
34297
+ return `${PRE_TOOL_USE_RESULT_HELPER}(await $\`echo \${${payload}()} | ${escapedCommand}\`.nothrow().quiet());`;
34298
+ }
34299
+ return `await $\`${escapedCommand}\`;`;
34300
+ }
34075
34301
  /** Emit the named (`tool.execute.*`) handler blocks. */
34076
34302
  function buildNamedEventBodyLines(namedEventHandlers) {
34077
34303
  const bodyLines = [];
34078
34304
  for (const [eventName, handlers] of Object.entries(namedEventHandlers)) {
34079
34305
  const matcherSubject = NAMED_HOOK_MATCHER_SUBJECTS[eventName] ?? null;
34080
- bodyLines.push(` "${eventName}": async (input) => {`);
34306
+ const stdinEvents = [...new Set(handlers.flatMap((handler) => handler.stdinEvent ? [handler.stdinEvent] : []))];
34307
+ bodyLines.push(` "${eventName}": async (${stdinEvents.length > 0 ? "input, output" : "input"}) => {`);
34308
+ for (const stdinEvent of stdinEvents) {
34309
+ const payload = stdinPayloadVariable(stdinEvent);
34310
+ bodyLines.push(` let ${payload}Cache;`);
34311
+ bodyLines.push(` const ${payload} = () =>`);
34312
+ bodyLines.push(` (${payload}Cache ??= JSON.stringify({`);
34313
+ bodyLines.push(` hook_event_name: "${stdinEvent}",`);
34314
+ bodyLines.push(" session_id: input.sessionID,");
34315
+ bodyLines.push(" tool_use_id: input.callID,");
34316
+ bodyLines.push(" tool_name: input.tool,");
34317
+ bodyLines.push(" tool_input: output?.args,");
34318
+ bodyLines.push(" cwd: directory ?? process.cwd(),");
34319
+ bodyLines.push(" }));");
34320
+ }
34081
34321
  for (const handler of handlers) {
34082
- const escapedCommand = escapeForTemplateLiteral(handler.command);
34322
+ const runStatement = buildRunStatement(handler);
34083
34323
  if (handler.toolGate) {
34084
34324
  bodyLines.push(` if (input.tool === "${handler.toolGate}") {`);
34085
- bodyLines.push(` await $\`${escapedCommand}\`;`);
34325
+ bodyLines.push(` ${runStatement}`);
34086
34326
  bodyLines.push(" }");
34087
34327
  } else if (handler.matcher && matcherSubject !== null) {
34088
34328
  const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
34089
34329
  bodyLines.push(" {");
34090
34330
  bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
34091
34331
  bodyLines.push(` if (__re.test(${matcherSubject})) {`);
34092
- bodyLines.push(` await $\`${escapedCommand}\`;`);
34332
+ bodyLines.push(` ${runStatement}`);
34093
34333
  bodyLines.push(" }");
34094
34334
  bodyLines.push(" }");
34095
- } else bodyLines.push(` await $\`${escapedCommand}\`;`);
34335
+ } else bodyLines.push(` ${runStatement}`);
34096
34336
  }
34097
34337
  bodyLines.push(" },");
34098
34338
  }
34099
34339
  return bodyLines;
34100
34340
  }
34101
34341
  /** Wrap the handler body lines in the requested export shape. */
34102
- function wrapInExportShape({ bodyLines, exportStyle }) {
34342
+ function wrapInExportShape({ preambleLines, bodyLines, exportStyle }) {
34343
+ const pluginContextPattern = preambleLines.length > 0 ? "{ $, directory }" : "{ $ }";
34103
34344
  const lines = [];
34104
34345
  if (exportStyle === "default") {
34105
34346
  lines.push("export default {");
34106
34347
  lines.push(" id: \"rulesync-hooks\",");
34107
- lines.push(" server: async ({ $ }) => {");
34348
+ lines.push(` server: async (${pluginContextPattern}) => {`);
34349
+ for (const line of preambleLines) lines.push(line === "" ? "" : ` ${line}`);
34108
34350
  lines.push(" return {");
34109
34351
  for (const line of bodyLines) lines.push(line === "" ? "" : ` ${line}`);
34110
34352
  lines.push(" };");
34111
34353
  lines.push(" },");
34112
34354
  lines.push("};");
34113
34355
  } else {
34114
- lines.push("export const RulesyncHooksPlugin = async ({ $ }) => {");
34356
+ lines.push(`export const RulesyncHooksPlugin = async (${pluginContextPattern}) => {`);
34357
+ for (const line of preambleLines) lines.push(line === "" ? "" : ` ${line}`);
34115
34358
  lines.push(" return {");
34116
34359
  lines.push(...bodyLines);
34117
34360
  lines.push(" };");
@@ -34136,8 +34379,10 @@ function generateOpencodeStylePluginCode(config, supportedEvents, toolConfigKey,
34136
34379
  namedEventHandlers,
34137
34380
  genericEventHandlers
34138
34381
  });
34382
+ const bodyLines = [...buildGenericEventBodyLines(genericEventHandlers), ...buildNamedEventBodyLines(namedEventHandlers)];
34139
34383
  return wrapInExportShape({
34140
- bodyLines: [...buildGenericEventBodyLines(genericEventHandlers), ...buildNamedEventBodyLines(namedEventHandlers)],
34384
+ preambleLines: Object.values(namedEventHandlers).some((handlers) => handlers.some((handler) => handler.stdinEvent !== void 0)) ? PRE_TOOL_USE_RESULT_HELPER_LINES : [],
34385
+ bodyLines,
34141
34386
  exportStyle
34142
34387
  }).join("\n");
34143
34388
  }
@@ -35282,36 +35527,12 @@ function failureLinesByMode(dialect) {
35282
35527
  * `PI_SETTLE_BLOCKING_EVENT`): such a failure is rethrown for Pi to report
35283
35528
  * instead of continuing the agent.
35284
35529
  *
35285
- * The chosen text is sanitized because a hook command's output can relay
35286
- * third-party content (linter output, matched file lines) into a terminal or,
35287
- * in RPC mode, into an external client. Escape sequences, C1 and other control
35288
- * characters, and format characters such as bidirectional overrides and
35289
- * zero-width joiners are stripped so the reason cannot repaint or reorder what
35290
- * the user is shown, and carriage returns are folded into newlines so it cannot
35291
- * overwrite an already printed line. The format-character pass is a superset of
35292
- * the bidi controls listed in `CONTROL_CHARACTERS_PATTERN`
35293
- * (`src/utils/control-characters.ts`): a blocked-hook reason is short
35294
- * diagnostic text, so dropping every `Cf` character is safer than enumerating
35295
- * the harmful ones and missing `U+061C`, `U+FEFF`, or a future addition.
35296
- *
35297
- * Sanitization runs before truncation so a reason that begins with a progress
35298
- * banner is still reported by its content. The raw text is only sliced to a
35299
- * generous backstop first: a hook command may emit up to `exec`'s full
35300
- * `maxBuffer`, and the slice bounds the work even if a later edit reintroduces
35301
- * a pattern that is not linear. A slice that actually fired is reported as
35302
- * truncated, so the text is never silently cut.
35303
- *
35304
- * Both slices cut on UTF-16 code units, so each drops a trailing lone high
35305
- * surrogate: the reason travels to an RPC client as JSON, and half a surrogate
35306
- * pair is not text a strict consumer has to accept.
35530
+ * The chosen text goes through `sanitizeBlockReason`, shared with the
35531
+ * OpenCode-style plugin (see `hook-block-reason.ts`): control and format
35532
+ * characters are stripped and the result is capped at 2000 characters.
35307
35533
  */
35308
35534
  const BLOCK_REASON_HELPER_LINES = [
35309
- "const MAX_BLOCK_REASON_LENGTH = 2000;",
35310
- "const MAX_SCANNED_REASON_LENGTH = 128_000;",
35311
- "",
35312
- "function dropTrailingLoneSurrogate(text: string): string {",
35313
- " return text.replace(/[\\ud800-\\udbff]$/, \"\");",
35314
- "}",
35535
+ ...buildBlockReasonSanitizerLines({ typed: true }),
35315
35536
  "",
35316
35537
  "function toBlockReason(error: unknown): string {",
35317
35538
  " const result = error as { stdout?: unknown; stderr?: unknown; code?: unknown } | null;",
@@ -35325,20 +35546,7 @@ const BLOCK_REASON_HELPER_LINES = [
35325
35546
  " : error instanceof Error",
35326
35547
  " ? error.message",
35327
35548
  " : String(error));",
35328
- " const scannedTruncated = raw.length > MAX_SCANNED_REASON_LENGTH;",
35329
- " const scanned = scannedTruncated",
35330
- " ? dropTrailingLoneSurrogate(raw.slice(0, MAX_SCANNED_REASON_LENGTH))",
35331
- " : raw;",
35332
- " const sanitized = scanned",
35333
- " .replace(/\\r\\n?/g, \"\\n\")",
35334
- " .replace(/\\u001b\\[[0-9;?]*[\\u0020-\\u002f]*[\\u0040-\\u007e]/g, \"\")",
35335
- " .replace(/\\u001b\\][^\\u0007\\u001b]{0,256}(?:\\u0007|\\u001b\\\\)/g, \"\")",
35336
- " .replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f-\\u009f]/g, \"\")",
35337
- " .replace(/[\\p{Cf}\\p{Zl}\\p{Zp}]/gu, \"\")",
35338
- " .trim();",
35339
- " if (!sanitized) return \"Hook command failed.\";",
35340
- " if (sanitized.length <= MAX_BLOCK_REASON_LENGTH && !scannedTruncated) return sanitized;",
35341
- " return `${dropTrailingLoneSurrogate(sanitized.slice(0, MAX_BLOCK_REASON_LENGTH))}...`;",
35549
+ " return sanitizeBlockReason(raw) || \"Hook command failed.\";",
35342
35550
  "}"
35343
35551
  ];
35344
35552
  /**
@@ -48957,12 +49165,18 @@ function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
48957
49165
  * - enabledTools/disabledTools -> top-level tools map (with server name prefix)
48958
49166
  * - OpenCode-supported extras (timeout, oauth, protocol, codemode) -> passed through verbatim
48959
49167
  */
48960
- function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, logger) {
49168
+ function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, configShape, logger) {
48961
49169
  const serverRecord = serverConfig;
48962
49170
  const passthrough = {};
48963
49171
  for (const key of OPENCODE_PASSTHROUGH_SERVER_FIELDS) if (serverRecord[key] !== void 0) passthrough[key] = serverRecord[key];
48964
49172
  const enabled = serverConfig.disabled !== void 0 ? !serverConfig.disabled : true;
48965
49173
  if (declaresNoTransport(serverConfig)) {
49174
+ if (configShape === "v2") return warnAndSkipMcpServer({
49175
+ toolName: "OpenCode",
49176
+ serverName,
49177
+ reason: "no transport, and the OpenCode V2 shape (\"configShape\": \"v2\") has no toggle-only entry",
49178
+ logger
49179
+ });
48966
49180
  if (serverConfig.disabled === void 0) {
48967
49181
  if (existingEntry !== void 0 && !isOpencodeTransportServer(existingEntry)) {
48968
49182
  warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
@@ -49010,11 +49224,11 @@ function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry,
49010
49224
  ...serverConfig.cwd && { cwd: serverConfig.cwd }
49011
49225
  };
49012
49226
  }
49013
- function convertToOpencodeFormat(mcpServers, existingMcp, logger) {
49227
+ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
49014
49228
  const tools = {};
49015
49229
  return {
49016
49230
  mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
49017
- const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], logger);
49231
+ const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], configShape, logger);
49018
49232
  if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[`${serverName}_${tool}`] = true;
49019
49233
  if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[`${serverName}_${tool}`] = false;
49020
49234
  return converted === null ? null : [serverName, converted];
@@ -49022,6 +49236,7 @@ function convertToOpencodeFormat(mcpServers, existingMcp, logger) {
49022
49236
  tools
49023
49237
  };
49024
49238
  }
49239
+ const OpencodeMcpFeatureOptionsSchema = z.looseObject({ configShape: z.optional(z.enum(["v1", "v2"])) });
49025
49240
  var OpencodeMcp = class extends ToolMcp {
49026
49241
  /** Directory layout; OpenCode forks (MiMo Code) override it. */
49027
49242
  static layout = OPENCODE_LAYOUT;
@@ -49060,6 +49275,19 @@ var OpencodeMcp = class extends ToolMcp {
49060
49275
  return this.json;
49061
49276
  }
49062
49277
  /**
49278
+ * Reads the `configShape` option of the target's `mcp` feature options
49279
+ * (`targets.opencode.mcp.configShape`). Only a layout that reads the V2
49280
+ * spellings back can be given the V2 shape.
49281
+ */
49282
+ static resolveConfigShape(options) {
49283
+ if (!options) return "v1";
49284
+ const parsed = OpencodeMcpFeatureOptionsSchema.safeParse(options);
49285
+ if (!parsed.success) throw new Error(`Invalid options for the ${this.layout.toolTarget} mcp feature: \`configShape\` must be either "v1" or "v2".`);
49286
+ const configShape = parsed.data.configShape ?? "v1";
49287
+ if (configShape === "v2" && !this.layout.readsV2ConfigSpellings) throw new Error(`Invalid options for the ${this.layout.toolTarget} mcp feature: \`configShape: "v2"\` is supported only for OpenCode.`);
49288
+ return configShape;
49289
+ }
49290
+ /**
49063
49291
  * opencode.json may contain other settings, so it should not be deleted.
49064
49292
  */
49065
49293
  isDeletable() {
@@ -49103,7 +49331,8 @@ var OpencodeMcp = class extends ToolMcp {
49103
49331
  logger
49104
49332
  });
49105
49333
  }
49106
- static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
49334
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger, featureOptions }) {
49335
+ const configShape = this.resolveConfigShape(featureOptions);
49107
49336
  const basePaths = this.getSettablePaths({ global });
49108
49337
  const jsonDir = join(outputRoot, basePaths.relativeDirPath);
49109
49338
  let fileContent = null;
@@ -49121,16 +49350,21 @@ var OpencodeMcp = class extends ToolMcp {
49121
49350
  });
49122
49351
  const existingJson = parse$1(fileContent || "{}");
49123
49352
  const existingMcp = OpencodeConfigSchema.safeParse(this.lowerConfig(existingJson));
49124
- if (this.layout.readsV2ConfigSpellings && isRecord$1(existingJson)) {
49125
- const containers = findOpencodeV2McpContainers(existingJson.mcp);
49353
+ const existingRawMcp = isRecord$1(existingJson) ? existingJson.mcp : void 0;
49354
+ if (configShape === "v1" && this.layout.readsV2ConfigSpellings) {
49355
+ const containers = findOpencodeV2McpContainers(existingRawMcp);
49126
49356
  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.`);
49127
49357
  }
49128
- const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
49129
- const generatedMcp = undefinedWhenEmpty(convertedMcp);
49358
+ const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, configShape, logger);
49359
+ const generatedServers = undefinedWhenEmpty(convertedMcp);
49360
+ const generatedMcp = configShape === "v2" ? this.toV2Mcp({
49361
+ servers: convertedMcp,
49362
+ existingRawMcp
49363
+ }) : generatedServers;
49130
49364
  const generatedTools = Object.keys(mcpTools).length > 0 ? mcpTools : void 0;
49131
49365
  warnOpencodeDotDirOverrides({
49132
49366
  generated: {
49133
- mcp: generatedMcp,
49367
+ mcp: generatedServers,
49134
49368
  tools: generatedTools
49135
49369
  },
49136
49370
  overlays: (await readOpencodeDotDirConfigLayers({
@@ -49166,6 +49400,28 @@ var OpencodeMcp = class extends ToolMcp {
49166
49400
  });
49167
49401
  }
49168
49402
  /**
49403
+ * Builds the V2 `mcp` value: each server raised to the V2 spelling under the
49404
+ * `mcp.servers` envelope, keeping what the existing file has that Rulesync
49405
+ * cannot express (see {@link raiseOpencodeServerToV2}), plus the existing
49406
+ * global `mcp.timeout`, which has no canonical counterpart.
49407
+ */
49408
+ static toV2Mcp({ servers, existingRawMcp }) {
49409
+ const v2Servers = {};
49410
+ for (const [name, server] of Object.entries(servers)) Object.defineProperty(v2Servers, name, {
49411
+ value: raiseOpencodeServerToV2(server, getOpencodeMcpServerEntry(existingRawMcp, name)),
49412
+ enumerable: true,
49413
+ configurable: true,
49414
+ writable: true
49415
+ });
49416
+ const globalTimeout = getOpencodeV2GlobalMcpTimeout(existingRawMcp);
49417
+ const hasServers = Object.keys(v2Servers).length > 0;
49418
+ if (!hasServers && globalTimeout === void 0) return;
49419
+ return {
49420
+ ...globalTimeout !== void 0 && { timeout: globalTimeout },
49421
+ ...hasServers && { servers: v2Servers }
49422
+ };
49423
+ }
49424
+ /**
49169
49425
  * Register additional instruction file paths into the shared opencode config
49170
49426
  * (`opencode.json` / `opencode.jsonc`) under the `instructions` key.
49171
49427
  *
@@ -54885,7 +55141,8 @@ var McpProcessor = class extends FeatureProcessor {
54885
55141
  toolTarget;
54886
55142
  global;
54887
55143
  getFactory;
54888
- constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, global = false, getFactory = defaultGetFactory$3, dryRun = false, logger }) {
55144
+ featureOptions;
55145
+ constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, global = false, getFactory = defaultGetFactory$3, dryRun = false, logger, featureOptions }) {
54889
55146
  super({
54890
55147
  outputRoot,
54891
55148
  inputRoots,
@@ -54897,6 +55154,7 @@ var McpProcessor = class extends FeatureProcessor {
54897
55154
  this.toolTarget = result.data;
54898
55155
  this.global = global;
54899
55156
  this.getFactory = getFactory;
55157
+ this.featureOptions = featureOptions;
54900
55158
  }
54901
55159
  /**
54902
55160
  * Implementation of abstract method from FeatureProcessor
@@ -54978,7 +55236,8 @@ var McpProcessor = class extends FeatureProcessor {
54978
55236
  outputRoot: this.outputRoot,
54979
55237
  rulesyncMcp: filteredRulesyncMcp,
54980
55238
  global: this.global,
54981
- logger: this.logger
55239
+ logger: this.logger,
55240
+ featureOptions: this.featureOptions
54982
55241
  });
54983
55242
  }));
54984
55243
  const auxiliaryFiles = await factory.class.getAuxiliaryFiles?.({
@@ -57759,12 +58018,14 @@ var CodewhaleSkill = class CodewhaleSkill extends ToolSkill {
57759
58018
  const CodexCliSkillFrontmatterSchema = z.looseObject({
57760
58019
  name: z.string(),
57761
58020
  description: z.string(),
57762
- metadata: z.optional(z.looseObject({ "short-description": z.optional(z.string()) }))
58021
+ metadata: z.optional(z.looseObject({ "short-description": z.optional(z.string()) })),
58022
+ "disable-model-invocation": z.optional(z.boolean())
57763
58023
  });
57764
58024
  /**
57765
58025
  * Relative path (within a skill directory) of the Codex `agents/openai.yaml` sidecar.
57766
58026
  * Codex CLI reads UI metadata, invocation policy, and tool dependencies from this file;
57767
- * `SKILL.md` frontmatter only carries `name` and `description`.
58027
+ * from `SKILL.md` frontmatter it reads only `name`, `description` and
58028
+ * `metadata.short-description`, ignoring any other key.
57768
58029
  * @see https://developers.openai.com/codex/skills.md
57769
58030
  */
57770
58031
  const CODEX_OPENAI_YAML_RELATIVE_PATH = CODEXCLI_OPENAI_YAML_RELATIVE_PATH;
@@ -57854,6 +58115,40 @@ function openaiYamlToCodexcliSection(parsed) {
57854
58115
  return Object.keys(section).length > 0 ? section : void 0;
57855
58116
  }
57856
58117
  /**
58118
+ * Build the `SKILL.md` `metadata` map: the root-level rulesync `metadata` with
58119
+ * the legacy `codexcli.short-description` merged in. The tool-specific value wins
58120
+ * over a root `metadata.short-description`, as a target section overrides the
58121
+ * root default everywhere else. Values are stringified because the Agent Skills
58122
+ * spec types `metadata` as a string-to-string map, and `.agents/skills/` is read
58123
+ * by other spec clients too; Codex itself deserializes `short-description` as a
58124
+ * string.
58125
+ * @see https://agentskills.io/specification
58126
+ */
58127
+ function buildSkillMetadata({ rootMetadata, shortDescription }) {
58128
+ const merged = {
58129
+ ...rootMetadata,
58130
+ ...shortDescription !== void 0 && { "short-description": shortDescription }
58131
+ };
58132
+ return Object.keys(merged).length > 0 ? toStringMetadata(merged) : void 0;
58133
+ }
58134
+ /**
58135
+ * Drop `policy.allow_implicit_invocation: false` from an imported sidecar section
58136
+ * when the `SKILL.md` frontmatter already carries `disable-model-invocation: true`,
58137
+ * since the root flag regenerates exactly that value. Keeping both would store the
58138
+ * same setting twice in `.rulesync/skills/`.
58139
+ */
58140
+ function dropPolicyImpliedByRootFlag(section, disableModelInvocation) {
58141
+ const policy = section?.policy;
58142
+ if (!section || disableModelInvocation !== true || policy === null || typeof policy !== "object" || Array.isArray(policy) || policy.allow_implicit_invocation !== false) return section;
58143
+ const { allow_implicit_invocation: _implied, ...restPolicy } = policy;
58144
+ const { policy: _policy, ...restSection } = section;
58145
+ const result = {
58146
+ ...restSection,
58147
+ ...Object.keys(restPolicy).length > 0 && { policy: restPolicy }
58148
+ };
58149
+ return Object.keys(result).length > 0 ? result : void 0;
58150
+ }
58151
+ /**
57857
58152
  * Represents a Codex CLI skill directory.
57858
58153
  * Codex CLI supports skills in both project mode (under $CWD/.agents/skills)
57859
58154
  * and global mode (under ~/.agents/skills — Rulesync resolves the home
@@ -57904,16 +58199,20 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
57904
58199
  }
57905
58200
  toRulesyncSkill() {
57906
58201
  const frontmatter = this.getFrontmatter();
58202
+ const disableModelInvocation = frontmatter["disable-model-invocation"];
57907
58203
  const { parsed, rest } = extractOpenaiYamlFile(this.getOtherFiles());
57908
- const openaiSection = openaiYamlToCodexcliSection(parsed);
58204
+ const openaiSection = dropPolicyImpliedByRootFlag(openaiYamlToCodexcliSection(parsed), disableModelInvocation);
58205
+ const { "short-description": shortDescription, ...rootMetadata } = frontmatter.metadata ?? {};
57909
58206
  const codexcliSection = {
57910
- ...frontmatter.metadata?.["short-description"] && { "short-description": frontmatter.metadata["short-description"] },
58207
+ ...shortDescription !== void 0 && { "short-description": shortDescription },
57911
58208
  ...openaiSection
57912
58209
  };
57913
58210
  const rulesyncFrontmatter = {
57914
58211
  name: frontmatter.name,
57915
58212
  description: frontmatter.description,
57916
58213
  targets: ["*"],
58214
+ ...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation },
58215
+ ...Object.keys(rootMetadata).length > 0 && { metadata: rootMetadata },
57917
58216
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
57918
58217
  };
57919
58218
  return new RulesyncSkill({
@@ -57930,10 +58229,16 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
57930
58229
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
57931
58230
  const settablePaths = CodexCliSkill.getSettablePaths({ global });
57932
58231
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
58232
+ const metadata = buildSkillMetadata({
58233
+ rootMetadata: rulesyncFrontmatter.metadata,
58234
+ shortDescription: rulesyncFrontmatter.codexcli?.["short-description"]
58235
+ });
58236
+ const disableModelInvocation = rulesyncFrontmatter["disable-model-invocation"];
57933
58237
  const codexFrontmatter = {
57934
58238
  name: rulesyncFrontmatter.name,
57935
58239
  description: rulesyncFrontmatter.description,
57936
- ...rulesyncFrontmatter.codexcli?.["short-description"] && { metadata: { "short-description": rulesyncFrontmatter.codexcli["short-description"] } }
58240
+ ...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation },
58241
+ ...metadata !== void 0 && { metadata }
57937
58242
  };
57938
58243
  const target = toPosixPath(CODEX_OPENAI_YAML_RELATIVE_PATH);
57939
58244
  const baseOtherFiles = rulesyncSkill.getOtherFiles().filter((file) => toPosixPath(file.relativeFilePathToDirPath) !== target);
@@ -67421,6 +67726,16 @@ var CopilotSubagent = class CopilotSubagent extends ToolSubagent {
67421
67726
  * (`preferred` lets a `subagents` settings override or the `/subagents`
67422
67727
  * picker replace the model; `required` locks dispatch to the authored
67423
67728
  * list). CLI v1.0.81+; the shared reference page still lists `model` only.
67729
+ * - `model` may also be a list of models in preference order. The Copilot
67730
+ * CLI changelog for v1.0.83 (2026-09-04) says "Custom agents can list
67731
+ * several models in `model`, tried in order until one is available to
67732
+ * you"; the CLI command reference and the shared configuration reference
67733
+ * still document `model` as a string, so older CLI versions may not read
67734
+ * the list form (a string is the safe form there). The VS Code Copilot
67735
+ * harness documents the same array spelling. Preserve a list as authored
67736
+ * instead of rewriting it to the separate `models` field.
67737
+ * https://github.com/github/copilot-cli/blob/main/changelog.md#1083---2026-09-04
67738
+ * https://github.com/microsoft/vscode-docs/blob/main/docs/agents/reference/custom-agent-file-reference.md
67424
67739
  *
67425
67740
  * `looseObject` is used so that future additional fields are passed through
67426
67741
  * unchanged (consistent with the rest of rulesync's frontmatter schemas).
@@ -67430,7 +67745,7 @@ const CopilotCliSubagentFrontmatterSchema = z.looseObject({
67430
67745
  name: z.optional(z.string()),
67431
67746
  target: z.optional(z.string()),
67432
67747
  tools: z.optional(z.union([z.string(), z.array(z.string())])),
67433
- model: z.optional(z.string()),
67748
+ model: z.optional(z.union([z.string(), z.array(z.string())])),
67434
67749
  models: z.optional(z.array(z.string())),
67435
67750
  modelPolicy: z.optional(z.enum(["preferred", "required"])),
67436
67751
  "disable-model-invocation": z.optional(z.boolean()),
@@ -107219,7 +107534,8 @@ function buildMcpStrategy(ctx) {
107219
107534
  toolTarget,
107220
107535
  global,
107221
107536
  dryRun,
107222
- logger
107537
+ logger,
107538
+ featureOptions: config.getFeatureOptions(toolTarget, "mcp")
107223
107539
  }),
107224
107540
  loadSource: (p) => p.loadToolFiles(),
107225
107541
  toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
@@ -108949,7 +109265,8 @@ async function generateMcpCore(params) {
108949
109265
  toolTarget,
108950
109266
  global: config.getGlobal(),
108951
109267
  dryRun: config.isPreviewMode(),
108952
- logger
109268
+ logger,
109269
+ featureOptions: config.getFeatureOptions(toolTarget, "mcp")
108953
109270
  });
108954
109271
  const result = await processFeatureWithRulesyncFiles({
108955
109272
  config,
@@ -109675,4 +109992,4 @@ async function importChecksCore(params) {
109675
109992
  //#endregion
109676
109993
  export { RulesyncIgnore as $, stripHiddenCharacters as $n, readFileContent as $t, caseFoldIdentity as A, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as An, ErrorCodes as At, QWENCODE_DIR as B, RULESYNC_RULES_RELATIVE_DIR_PATH as Bn, ensureDir as Bt, COMMANDCODE_DIR as C, MAX_FILE_SIZE as Cn, WarningCollectingLogger as Ct, ChecksProcessor as D, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Dn, resetRunWarningState as Dt, CODEBUDDY_LOCAL_RULE_FILE_NAME as E, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as En, withFallbackLoggerTarget as Et, CLAUDECODE_DIR as F, RULESYNC_MCP_SCHEMA_URL as Fn, assertTreeContainsNoSymlinks as Ft, RulesyncSubagent as G, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Gn, isFileNotFoundError as Gt, AUGMENTCODE_DIR as H, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Hn, findFilesByGlobs as Ht, CLAUDECODE_LOCAL_RULE_FILE_NAME as I, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as In, assertWritablePathInsideRoot as It, RulesyncSkillFrontmatterSchema as J, hasDeceptiveHiddenCharacters as Jn, listDirectoryEntryNames as Jt, RulesyncSubagentFrontmatterSchema as K, formatError as Kn, isFileSystemError as Kt, CLAUDECODE_MEMORIES_DIR_NAME as L, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Ln, checkPathTraversal as Lt, CODEXCLI_BASH_RULES_FILE_NAME as M, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Mn, applyFileMode as Mt, CODEXCLI_DIR as N, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Nn, assertDirectoryIfExists as Nt, FACTORYDROID_DIR as O, RULESYNC_CONFIG_SCHEMA_URL as On, withWarnOnceScope as Ot, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as P, RULESYNC_MCP_RELATIVE_FILE_PATH as Pn, assertPathResolvesInsideRoot as Pt, RulesyncMcp as Q, stripControlCharactersKeepingLineFeeds as Qn, posixRelativePathEscapesRoot as Qt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as R, RULESYNC_PERMISSIONS_SCHEMA_URL as Rn, createTempDirectory as Rt, LETTACODE_SETTINGS_LOCAL_FILE_NAME as S, FEATURE_SOURCE_TREE_ENTRIES as Sn, JsonLogger as St, CODEBUDDY_DIR as T, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Tn, warnOnConflictingFlags as Tt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as U, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Un, getFileSizeAndExecutableBit as Ut, QWENCODE_LOCAL_RULE_FILE_NAME as V, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Vn, fileExists as Vt, getLocalSkillDirNames as W, RULESYNC_USER_CONFIG_DIR_NAME as Wn, getHomeDirectory as Wt, RulesyncRuleFrontmatterSchema as X, quoteForLog as Xn, listSubdirectoryNames as Xt, RulesyncRule as Y, hasEnclosingMarkOutsideKeycap as Yn, listFilePathsRecursively as Yt, RulesyncPermissions as Z, stripControlCharacters as Zn, pathEscapesRoot as Zt, HooksProcessor as _, ToolTargetSchema as _n, GITIGNORE_DESTINATION_KEY as _t, convertFromTool as a, removeTempDirectory as an, RulesyncCommandFrontmatterSchema as at, REASONIX_LOCAL_RULE_FILE_NAME as b, DEPRECATED_FEATURE_REPLACEMENTS as bn, findControlCharacter as bt, parseCommaSeparatedList as c, runWithDirectoryRollback as cn, stringifyFrontmatter as ct, shortenToWidth as d, writeFileBuffer as dn, SKILL_FILE_NAME as dt, readFileContentOrNull as en, RulesyncHooks as et, RulesProcessor as f, writeFileContent as fn, ConfigResolver as ft, IgnoreProcessor as g, PACKAGING_TOOL_TARGETS as gn, ConfigFileSchema as gt, McpProcessor as h, ALL_TOOL_TARGETS_WITH_WILDCARD as hn, CONFLICTING_TARGET_PAIRS as ht, inspectInputRoots as i, removeFileStrict as in, RulesyncCommand as it, groupSpellingsByCaseFoldedIdentity as j, RULESYNC_HOOKS_RELATIVE_FILE_PATH as jn, PathOutsideRootError as jt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as k, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as kn, CLIError as kt, ELLIPSIS_WIDTH as l, splitPathSegments as ln, loadYaml as lt, SkillsProcessor as m, ALL_TOOL_TARGETS as mn, resolveEffectiveInputRoots as mt, formatSourceLoadFailure as n, removeDirectoryStrict as nn, resolveRulesyncSourceWritePath as nt, isPackagingToolTarget as o, resolvePath as on, RulesyncCheck as ot, SubagentsProcessor as p, mapWithConcurrency as pn, mergeInputRootConfigs as pt, RulesyncSkill as q, truncateText as qn, isSymlink as qt, generate as r, removeFile as rn, parseJsonc as rt, getProcessorRegistryEntry as s, resolvedRelativePath as sn, RulesyncCheckFrontmatterSchema as st, importFromTool as t, removeDirectory as tn, getRulesyncSourceCandidates as tt, displayWidthOf as u, toPosixPath as un, SHARED_USER_MANAGED_CONFIG_PATHS as ut, CRUSH_LOCAL_RULE_FILE_NAME as v, ALL_FEATURES as vn, SourceEntrySchema as vt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as w, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as wn, fallbackLogger as wt, LETTACODE_DIR as x, CURATED_RULES_FEATURE_SUBDIR as xn, ConsoleLogger as xt, CommandsProcessor as y, ALL_FEATURES_WITH_WILDCARD as yn, assertTargetsFeaturesExclusive as yt, CLAUDECODE_SKILLS_DIR_PATH as z, RULESYNC_RELATIVE_DIR_PATH as zn, directoryExists as zt };
109677
109994
 
109678
- //# sourceMappingURL=import-BmBQ8sWz.js.map
109995
+ //# sourceMappingURL=import-D7cJEvia.js.map