rulesync 31.0.0 → 33.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4823,13 +4823,11 @@ const PI_HOOK_EVENTS = [
4823
4823
  /**
4824
4824
  * Hook events supported by oh-my-pi (`omp`), Pi's fork, through the same
4825
4825
  * generated TypeScript extension. oh-my-pi keeps Pi's names for every event
4826
- * listed here, but it has neither `agent_before_settle` nor `ui_prompt_start`,
4827
- * so `stop` and `notification` are not mapped yet. oh-my-pi's stop surface is
4828
- * `session_stop`, which takes a Claude-compatible `{ decision: "block", reason }`
4829
- * result; it needs its own handler in the generator and is left for a
4830
- * follow-up. See CANONICAL_TO_OMP_EVENT_NAMES.
4826
+ * listed here except `stop`: it has no `agent_before_settle`, so `stop` maps to
4827
+ * its own `session_stop` event instead. It has no `ui_prompt_start` either, so
4828
+ * `notification` is not mapped. See CANONICAL_TO_OMP_EVENT_NAMES.
4831
4829
  *
4832
- * @see https://github.com/can1357/oh-my-pi/blob/40e9368ef0458fd9073329cdff4174895f91bc6b/docs/extensions.md
4830
+ * @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/extensions.md
4833
4831
  */
4834
4832
  const OMP_HOOK_EVENTS = [
4835
4833
  "sessionStart",
@@ -4840,6 +4838,7 @@ const OMP_HOOK_EVENTS = [
4840
4838
  "preModelInvocation",
4841
4839
  "postModelInvocation",
4842
4840
  "beforeSubmitPrompt",
4841
+ "stop",
4843
4842
  "preCompact",
4844
4843
  "postCompact"
4845
4844
  ];
@@ -6261,9 +6260,10 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
6261
6260
  /**
6262
6261
  * Map canonical camelCase event names to oh-my-pi extension event names. They
6263
6262
  * are Pi's names (see CANONICAL_TO_PI_EVENT_NAMES) for the events oh-my-pi
6264
- * kept. Note that oh-my-pi runs `session_shutdown` handlers with a 2-second
6265
- * budget, and fails a `tool_call` closed when its handler exceeds
6266
- * `extensionHandlers.toolCallTimeoutMs` (30 seconds by default).
6263
+ * kept, plus `session_stop` for `stop`. Note that oh-my-pi runs
6264
+ * `session_shutdown` handlers with a 2-second budget, and fails a `tool_call`
6265
+ * closed when its handler exceeds `extensionHandlers.toolCallTimeoutMs`
6266
+ * (30 seconds by default).
6267
6267
  *
6268
6268
  * @see https://github.com/can1357/oh-my-pi/blob/40e9368ef0458fd9073329cdff4174895f91bc6b/docs/extensions.md
6269
6269
  */
@@ -6276,6 +6276,7 @@ const CANONICAL_TO_OMP_EVENT_NAMES = {
6276
6276
  preModelInvocation: "context",
6277
6277
  postModelInvocation: "message_end",
6278
6278
  beforeSubmitPrompt: "input",
6279
+ stop: "session_stop",
6279
6280
  preCompact: "session_before_compact",
6280
6281
  postCompact: "session_compact"
6281
6282
  };
@@ -12933,10 +12934,16 @@ const SHARED_CONFIG_OWNERSHIP = {
12933
12934
  [CODEWHALE_CONFIG_SHARED_FILE_KEY]: {
12934
12935
  format: "toml",
12935
12936
  invalidRootPolicy: "error",
12936
- features: { hooks: {
12937
- kind: "replace-owned-keys",
12938
- ownedKeys: ["hooks"]
12939
- } }
12937
+ features: {
12938
+ hooks: {
12939
+ kind: "replace-owned-keys",
12940
+ ownedKeys: ["hooks"]
12941
+ },
12942
+ permissions: {
12943
+ kind: "replace-owned-keys",
12944
+ ownedKeys: ["allow_shell"]
12945
+ }
12946
+ }
12940
12947
  },
12941
12948
  ".letta/settings.json": {
12942
12949
  format: "json",
@@ -21737,22 +21744,29 @@ function mergeOpencodeV2Entries({ config, legacyKey, pluralKey, lower }) {
21737
21744
  const entry = asOpencodeEntries(value);
21738
21745
  const lowered = entry === null ? null : lower(entry);
21739
21746
  if (Object.hasOwn(merged, name) || lowered === null) continue;
21740
- Object.defineProperty(merged, name, {
21741
- value: lowered,
21742
- enumerable: true,
21743
- configurable: true,
21744
- writable: true
21745
- });
21747
+ setOwnEntry(merged, name, lowered);
21746
21748
  }
21747
21749
  return Object.keys(merged).length > 0 ? merged : legacy;
21748
21750
  }
21749
21751
  /**
21752
+ * Sets `target[key]` as an own, enumerable property. A plain assignment to a
21753
+ * key named `__proto__` would set the prototype instead.
21754
+ */
21755
+ function setOwnEntry(target, key, value) {
21756
+ Object.defineProperty(target, key, {
21757
+ value,
21758
+ enumerable: true,
21759
+ configurable: true,
21760
+ writable: true
21761
+ });
21762
+ }
21763
+ /**
21750
21764
  * Returns the inline agent entries of an OpenCode config in the V1 `agent`
21751
21765
  * shape. Since v1.18.24 OpenCode V1 also reads the V2 `agents` spelling and
21752
21766
  * lowers it, so a layout that does so (`readsV2ConfigSpellings`) gets those
21753
21767
  * entries too.
21754
21768
  *
21755
- * @see https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/config/v2-compat.ts
21769
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
21756
21770
  */
21757
21771
  function getOpencodeAgentEntries({ config, layout = OPENCODE_LAYOUT }) {
21758
21772
  if (!layout.readsV2ConfigSpellings) return asOpencodeEntries(config.agent);
@@ -21771,7 +21785,7 @@ function getOpencodeAgentEntries({ config, layout = OPENCODE_LAYOUT }) {
21771
21785
  * Other fields are kept: V1 drops a V2 `subagent`, but rulesync reads it as
21772
21786
  * `subtask` everywhere (as V2 does), so it is left for the command importer.
21773
21787
  *
21774
- * @see https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/config/v2-compat.ts
21788
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
21775
21789
  */
21776
21790
  function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
21777
21791
  if (!layout.readsV2ConfigSpellings) return asOpencodeEntries(config.command);
@@ -21794,7 +21808,7 @@ function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
21794
21808
  * reads V2 spellings, a flat V2 `skills` array is accepted too: its entries
21795
21809
  * that are not `http(s)://` URLs are the paths, as OpenCode V1 lowers them.
21796
21810
  *
21797
- * @see https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/config/v2-compat.ts
21811
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
21798
21812
  */
21799
21813
  function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
21800
21814
  if (Array.isArray(config.skills)) {
@@ -21804,6 +21818,160 @@ function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
21804
21818
  const skills = asOpencodeEntries(config.skills);
21805
21819
  return skills !== null && Array.isArray(skills.paths) ? skills.paths : [];
21806
21820
  }
21821
+ /**
21822
+ * Whether a record is itself an MCP server entry rather than a V2 container
21823
+ * (`mcp.servers`, `mcp.timeout`): a server — or a server literally named
21824
+ * `type` / `enabled` — carries a scalar `type` or `enabled`. Mirrors OpenCode
21825
+ * V1's `isDirectServer`.
21826
+ */
21827
+ function isDirectOpencodeMcpServer(value) {
21828
+ return ["type", "enabled"].some((key) => Object.hasOwn(value, key) && (value[key] === null || typeof value[key] !== "object" || Array.isArray(value[key])));
21829
+ }
21830
+ const OPENCODE_V2_TIMEOUT_KEYS = [
21831
+ "startup",
21832
+ "catalog",
21833
+ "execution"
21834
+ ];
21835
+ /**
21836
+ * Whether an MCP server entry uses a V2-only per-server spelling, so that
21837
+ * OpenCode V1 lowers it: `disabled`, `codemode`, an object `timeout`, or a
21838
+ * snake_case `oauth` key.
21839
+ */
21840
+ function isOpencodeV2NativeServer(value) {
21841
+ const oauth = value.oauth;
21842
+ return Object.hasOwn(value, "disabled") || Object.hasOwn(value, "codemode") || isRecord$1(value.timeout) || isRecord$1(oauth) && [
21843
+ "client_id",
21844
+ "client_secret",
21845
+ "callback_port",
21846
+ "redirect_uri"
21847
+ ].some((key) => Object.hasOwn(oauth, key));
21848
+ }
21849
+ /**
21850
+ * Lowers a V2 timeout object (`{ startup, catalog, execution }`) to V1's single
21851
+ * number, the way OpenCode V1 does: only a `catalog` equal to `execution`, with
21852
+ * no `startup`, has a V1 equivalent. Anything else — including a value that is
21853
+ * not a positive integer — is `undefined` (dropped).
21854
+ */
21855
+ function lowerOpencodeV2Timeout(value) {
21856
+ if (!hasPositiveIntegerTimeouts(value) || value.startup !== void 0 || typeof value.catalog !== "number") return;
21857
+ return value.catalog === value.execution ? value.catalog : void 0;
21858
+ }
21859
+ const OPENCODE_V2_OAUTH_KEYS = /* @__PURE__ */ new Map([
21860
+ ["client_id", "clientId"],
21861
+ ["client_secret", "clientSecret"],
21862
+ ["callback_port", "callbackPort"],
21863
+ ["redirect_uri", "redirectUri"]
21864
+ ]);
21865
+ /**
21866
+ * V2 `oauth` keys that have no V1 spelling. OpenCode V1 drops them when it
21867
+ * lowers a server, so they are dropped (and reported) here too rather than
21868
+ * being written back as a snake_case key among camelCase ones.
21869
+ */
21870
+ const OPENCODE_V2_UNSUPPORTED_OAUTH_KEYS = /* @__PURE__ */ new Set(["auth_server_metadata_url"]);
21871
+ function lowerOpencodeV2OAuth(oauth, path, onDropped) {
21872
+ const lowered = {};
21873
+ for (const [key, value] of Object.entries(oauth)) {
21874
+ if (OPENCODE_V2_UNSUPPORTED_OAUTH_KEYS.has(key)) {
21875
+ onDropped?.(`${path}.${key}`, "unsupported");
21876
+ continue;
21877
+ }
21878
+ setOwnEntry(lowered, OPENCODE_V2_OAUTH_KEYS.get(key) ?? key, value);
21879
+ }
21880
+ return lowered;
21881
+ }
21882
+ /**
21883
+ * Lowers one V2-spelled MCP server entry to V1's shape, as OpenCode V1 does:
21884
+ * `disabled` becomes `enabled` (an explicit `enabled` wins), an object
21885
+ * `timeout` becomes a number or is dropped, and snake_case `oauth` keys become
21886
+ * camelCase. `codemode` is kept: Rulesync passes it through where V1 drops it.
21887
+ *
21888
+ * Like V1, only an entry that is a local or remote server is lowered, and one
21889
+ * whose `enabled` or `disabled` is not a boolean is left as it is. Unlike V1,
21890
+ * such an entry is not dropped but passed on, so the schema rejects it loudly.
21891
+ */
21892
+ function lowerOpencodeV2Server(value, path, onDropped) {
21893
+ if (!isRecord$1(value) || value.type !== "local" && value.type !== "remote" || Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean" || Object.hasOwn(value, "disabled") && typeof value.disabled !== "boolean") return value;
21894
+ const { disabled, timeout, ...rest } = value;
21895
+ const lowered = { ...rest };
21896
+ if (!Object.hasOwn(value, "enabled")) lowered.enabled = disabled !== true;
21897
+ else if (value.enabled === disabled) onDropped?.(`${path}.disabled`, "conflict");
21898
+ const lowTimeout = isRecord$1(timeout) ? lowerOpencodeV2Timeout(timeout) : timeout;
21899
+ if (lowTimeout !== void 0) lowered.timeout = lowTimeout;
21900
+ else if (isRecord$1(timeout) && Object.keys(timeout).length > 0) onDropped?.(`${path}.timeout`, "unsupported");
21901
+ if (isRecord$1(value.oauth)) lowered.oauth = lowerOpencodeV2OAuth(value.oauth, `${path}.oauth`, onDropped);
21902
+ return lowered;
21903
+ }
21904
+ function isPositiveInteger(value) {
21905
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
21906
+ }
21907
+ /** Whether each V2 timeout value a record carries is a positive integer. */
21908
+ function hasPositiveIntegerTimeouts(value) {
21909
+ return OPENCODE_V2_TIMEOUT_KEYS.every((key) => value[key] === void 0 || isPositiveInteger(value[key]));
21910
+ }
21911
+ /**
21912
+ * Whether `mcp.timeout` is V2's global timeout object rather than a server
21913
+ * named `timeout`. As in OpenCode V1, each of its values must be a positive
21914
+ * integer; anything else is read as a server, which the schema then rejects.
21915
+ */
21916
+ function isOpencodeV2GlobalTimeout(value) {
21917
+ return isRecord$1(value) && !isDirectOpencodeMcpServer(value) && (Object.keys(value).length === 0 || OPENCODE_V2_TIMEOUT_KEYS.some((key) => Object.hasOwn(value, key))) && hasPositiveIntegerTimeouts(value);
21918
+ }
21919
+ function getOpencodeV2McpEnvelope(mcp) {
21920
+ const nested = mcp.servers;
21921
+ return isRecord$1(nested) && !isDirectOpencodeMcpServer(nested) ? nested : null;
21922
+ }
21923
+ /**
21924
+ * Returns the paths of the V2-only containers in an OpenCode `mcp` value that
21925
+ * a write in V1's flat shape does not keep: a non-empty `mcp.servers`
21926
+ * envelope, and a non-empty global `mcp.timeout`.
21927
+ */
21928
+ function findOpencodeV2McpContainers(mcp) {
21929
+ if (!isRecord$1(mcp)) return [];
21930
+ const envelope = getOpencodeV2McpEnvelope(mcp);
21931
+ return [...envelope !== null && Object.keys(envelope).length > 0 ? ["mcp.servers"] : [], ...isOpencodeV2GlobalTimeout(mcp.timeout) && Object.keys(mcp.timeout).length > 0 ? ["mcp.timeout"] : []];
21932
+ }
21933
+ /**
21934
+ * Lowers OpenCode V2 `mcp` spellings found in a V1 config to the flat V1 map,
21935
+ * the way OpenCode V1 itself does since v1.18.24 (`normalizeMcp`):
21936
+ *
21937
+ * - entries of the `mcp.servers` envelope are merged into the map, a flat entry
21938
+ * of the same name winning;
21939
+ * - V2-spelled server entries (`disabled`, an object `timeout`, snake_case
21940
+ * `oauth` keys) are lowered by {@link lowerOpencodeV2Server};
21941
+ * - the global `mcp.timeout` object is skipped. V1 moves it to
21942
+ * `experimental.mcp_timeout`, a default request timeout that has no
21943
+ * canonical counterpart, so it is not read as a server either.
21944
+ *
21945
+ * Unlike V1, an envelope entry that is not a valid server is not dropped: it
21946
+ * is passed on, so the schema rejects it loudly rather than it vanishing
21947
+ * without a word. Each value that is dropped — a non-empty global timeout, a
21948
+ * per-server timeout object or `oauth` key with no V1 equivalent, or an
21949
+ * envelope entry shadowed by a different flat entry — is reported through
21950
+ * `onDropped`.
21951
+ *
21952
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
21953
+ * @see https://opencode.ai/v2/docs/mcp-servers/
21954
+ */
21955
+ function lowerOpencodeV2Mcp(mcp, onDropped) {
21956
+ if (!isRecord$1(mcp)) return mcp;
21957
+ const envelope = getOpencodeV2McpEnvelope(mcp);
21958
+ const globalTimeout = isOpencodeV2GlobalTimeout(mcp.timeout);
21959
+ if (findOpencodeV2McpContainers(mcp).includes("mcp.timeout")) onDropped?.("mcp.timeout", "unsupported");
21960
+ const servers = {};
21961
+ for (const [name, value] of Object.entries(mcp)) {
21962
+ if (name === "servers" && envelope !== null || name === "timeout" && globalTimeout) continue;
21963
+ setOwnEntry(servers, name, isRecord$1(value) && isOpencodeV2NativeServer(value) ? lowerOpencodeV2Server(value, `mcp.${name}`, onDropped) : value);
21964
+ }
21965
+ for (const [name, value] of Object.entries(envelope ?? {})) {
21966
+ const path = `mcp.servers.${name}`;
21967
+ if (Object.hasOwn(servers, name)) {
21968
+ if (!isDeepStrictEqual(servers[name], value)) onDropped?.(path, "conflict");
21969
+ continue;
21970
+ }
21971
+ setOwnEntry(servers, name, lowerOpencodeV2Server(value, path, onDropped));
21972
+ }
21973
+ return servers;
21974
+ }
21807
21975
  /** Matches a string whose entire value is an OpenCode `{file:...}` reference. */
21808
21976
  const OPENCODE_FILE_TEMPLATE_PATTERN = /^\s*\{file:(.+?)\}\s*$/;
21809
21977
  /**
@@ -32835,6 +33003,31 @@ const PI_PROMPT_BLOCKING_EVENT = "input";
32835
33003
  * @see https://github.com/earendil-works/pi/blob/v0.87.1/packages/coding-agent/src/core/extensions/types.ts
32836
33004
  */
32837
33005
  const PI_SETTLE_BLOCKING_EVENT = "agent_before_settle";
33006
+ /**
33007
+ * `session_stop` is oh-my-pi's stop surface (it has no `agent_before_settle`).
33008
+ * It fires when a main-session run is about to settle (never for subagents,
33009
+ * nor for a run the user aborted), and a handler returning
33010
+ * `{ continue: true, additionalContext }` queues one more turn with that text
33011
+ * as hidden model-visible context. As on Pi, only a stop command exiting with
33012
+ * code 2 asks for that, the feedback of every such command is combined, and
33013
+ * any other failure is rethrown for oh-my-pi to report. The generated handler
33014
+ * continues only when `event.stop_hook_active` is false, which oh-my-pi sets
33015
+ * after a continuation and clears once a stop pass lets the run settle, so the
33016
+ * agent is continued at most once per stop (the flag is shared by every
33017
+ * extension, so another extension's continuation also suppresses ours);
33018
+ * oh-my-pi additionally caps these
33019
+ * advisory continuations at 8 in a row. A run that ended on a provider error
33020
+ * (`stopReason: "error"`) is not continued, matching Pi's `completed`-only
33021
+ * gate. The advisory form is used rather than `{ decision: "block", reason }`,
33022
+ * which oh-my-pi keeps enforcing without that cap. oh-my-pi gives
33023
+ * `session_stop` handlers the 30-second extension handler budget: stop
33024
+ * commands running longer time the handler out, the run settles without a
33025
+ * continuation, and the command is not killed.
33026
+ *
33027
+ * @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/extensions.md
33028
+ * @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/packages/coding-agent/src/session/agent-session.ts
33029
+ */
33030
+ const OMP_SESSION_STOP_BLOCKING_EVENT = "session_stop";
32838
33031
  const PI_EXTENSION_DIALECT = {
32839
33032
  overrideKey: "pi",
32840
33033
  typesPackage: "@earendil-works/pi-coding-agent",
@@ -32856,7 +33049,8 @@ const OMP_EXTENSION_DIALECT = {
32856
33049
  const PI_BLOCKING_MODE_BY_EVENT = {
32857
33050
  [PI_BLOCKING_EVENT]: "tool",
32858
33051
  [PI_PROMPT_BLOCKING_EVENT]: "prompt",
32859
- [PI_SETTLE_BLOCKING_EVENT]: "settle"
33052
+ [PI_SETTLE_BLOCKING_EVENT]: "settle",
33053
+ [OMP_SESSION_STOP_BLOCKING_EVENT]: "sessionStop"
32860
33054
  };
32861
33055
  /**
32862
33056
  * The body emitted inside a blocking handler's `catch`, per gate. `tool_call`
@@ -32869,7 +33063,8 @@ function failureLinesByMode(dialect) {
32869
33063
  none: [],
32870
33064
  tool: ["return { block: true, reason: toBlockReason(error) };"],
32871
33065
  prompt: ["reportPromptGateFailure(ctx, toBlockReason(error));", dialect.promptCancelLine],
32872
- settle: ["stopHookFailures.push(error);"]
33066
+ settle: ["stopHookFailures.push(error);"],
33067
+ sessionStop: ["stopHookFailures.push(error);"]
32873
33068
  };
32874
33069
  }
32875
33070
  /**
@@ -32961,6 +33156,12 @@ const PROMPT_GATE_HELPER_LINES = [
32961
33156
  " console.error(reason);",
32962
33157
  "}"
32963
33158
  ];
33159
+ /** Helper emitted alongside every stop gate (Pi's settle gate and oh-my-pi's `session_stop`). */
33160
+ const STOP_HOOK_BLOCK_HELPER_LINES = [
33161
+ "function isStopHookBlock(error: unknown): boolean {",
33162
+ " return (error as { code?: unknown } | null)?.code === 2;",
33163
+ "}"
33164
+ ];
32964
33165
  /**
32965
33166
  * Helper emitted alongside the stop hook's settle gate. `event.entries` holds
32966
33167
  * the drafts earlier extensions proposed at this boundary, so they are kept and
@@ -32971,10 +33172,6 @@ const PROMPT_GATE_HELPER_LINES = [
32971
33172
  * appended message is committed, and Pi re-validates the final context itself.
32972
33173
  */
32973
33174
  const STOP_HOOK_HELPER_LINES = [
32974
- "function isStopHookBlock(error: unknown): boolean {",
32975
- " return (error as { code?: unknown } | null)?.code === 2;",
32976
- "}",
32977
- "",
32978
33175
  "function continueAfterStopHook(",
32979
33176
  " event: AgentBeforeSettleEvent,",
32980
33177
  " reason: string,",
@@ -33057,14 +33254,16 @@ function buildSubscriptionLines(handlerGroups, dialect) {
33057
33254
  const blocking = PI_BLOCKING_MODE_BY_EVENT[piEvent] ?? "none";
33058
33255
  const isPromptGate = blocking === "prompt";
33059
33256
  const isSettleGate = blocking === "settle";
33257
+ const isSessionStopGate = blocking === "sessionStop";
33258
+ const collectsStopFailures = isSettleGate || isSessionStopGate;
33060
33259
  const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
33061
33260
  const usesErrorFlag = handlers.some((h) => h.onlyOnError);
33062
33261
  const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
33063
- const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate || isSettleGate ? "event" : "";
33262
+ const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate || collectsStopFailures ? "event" : "";
33064
33263
  lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${params}) => {`);
33065
33264
  if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
33066
33265
  if (isPromptGate) lines.push(` if (event.source === "extension") ${dialect.promptPassLine}`);
33067
- if (isSettleGate) lines.push(" const stopHookFailures: unknown[] = [];");
33266
+ if (collectsStopFailures) lines.push(" const stopHookFailures: unknown[] = [];");
33068
33267
  for (const handler of handlers) lines.push(...buildCommandLines({
33069
33268
  handler,
33070
33269
  usesToolName,
@@ -33080,6 +33279,17 @@ function buildSubscriptionLines(handlerGroups, dialect) {
33080
33279
  lines.push(" }");
33081
33280
  lines.push(" if (stopHookFailures.length > 0) throw stopHookFailures[0];");
33082
33281
  }
33282
+ if (isSessionStopGate) {
33283
+ lines.push(" const blocks = stopHookFailures.filter(isStopHookBlock);");
33284
+ lines.push(" const endedOnError = (event.last_assistant_message as { stopReason?: unknown } | undefined)?.stopReason === \"error\";");
33285
+ lines.push(" if (blocks.length > 0 && !event.stop_hook_active && !endedOnError) {");
33286
+ lines.push(" return {");
33287
+ lines.push(" continue: true,");
33288
+ lines.push(" additionalContext: `Stop hook feedback:\\n${blocks.map(toBlockReason).join(\"\\n\\n\")}`,");
33289
+ lines.push(" };");
33290
+ lines.push(" }");
33291
+ lines.push(" if (stopHookFailures.length > 0) throw stopHookFailures[0];");
33292
+ }
33083
33293
  lines.push(" });");
33084
33294
  if (isSettleGate) {
33085
33295
  lines.push(` pi.on("agent_settled", async () => {`);
@@ -33098,8 +33308,9 @@ function buildSubscriptionLines(handlerGroups, dialect) {
33098
33308
  * `{ block: true, reason }`, and on `input` — Pi's prompt-submission gate —
33099
33309
  * where a non-zero exit cancels the prompt with the dialect's cancel result
33100
33310
  * (`{ action: "handled" }` for Pi, `{ handled: true }` for oh-my-pi), and
33101
- * on `agent_before_settle`, where a stop command exiting with code 2 asks the
33102
- * agent to continue once with the command's output as feedback.
33311
+ * on `agent_before_settle` (Pi) / `session_stop` (oh-my-pi), where a stop
33312
+ * command exiting with code 2 asks the agent to continue once with the
33313
+ * command's output as feedback.
33103
33314
  * `postToolUse` and `postToolUseFailure` share Pi's `tool_result` event; the
33104
33315
  * latter's commands run only when `event.isError` is set.
33105
33316
  *
@@ -33120,7 +33331,8 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
33120
33331
  const subscriptionLines = buildSubscriptionLines(handlerGroups, dialect);
33121
33332
  const hasPromptGate = Boolean(handlerGroups[PI_PROMPT_BLOCKING_EVENT]);
33122
33333
  const hasSettleGate = Boolean(handlerGroups[PI_SETTLE_BLOCKING_EVENT]);
33123
- const needsBlockReasonHelper = Boolean(handlerGroups[PI_BLOCKING_EVENT]) || hasPromptGate || hasSettleGate;
33334
+ const hasSessionStopGate = Boolean(handlerGroups[OMP_SESSION_STOP_BLOCKING_EVENT]);
33335
+ const needsBlockReasonHelper = Boolean(handlerGroups[PI_BLOCKING_EVENT]) || hasPromptGate || hasSettleGate || hasSessionStopGate;
33124
33336
  const lines = ["// Generated by rulesync. Do not edit manually."];
33125
33337
  if (subscriptionLines.length === 0) {
33126
33338
  lines.push("export default function () {}");
@@ -33147,6 +33359,10 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
33147
33359
  lines.push(...PROMPT_GATE_HELPER_LINES);
33148
33360
  lines.push("");
33149
33361
  }
33362
+ if (hasSettleGate || hasSessionStopGate) {
33363
+ lines.push(...STOP_HOOK_BLOCK_HELPER_LINES);
33364
+ lines.push("");
33365
+ }
33150
33366
  if (hasSettleGate) {
33151
33367
  lines.push(...STOP_HOOK_HELPER_LINES);
33152
33368
  lines.push("");
@@ -45504,18 +45720,21 @@ var KiroMcp = class KiroMcp extends ToolMcp {
45504
45720
  };
45505
45721
  //#endregion
45506
45722
  //#region src/features/mcp/opencode-mcp.ts
45723
+ const OpencodeMcpV2DisabledSchema = z.optional(z.boolean());
45507
45724
  const OpencodeMcpLocalServerSchema = z.looseObject({
45508
45725
  type: z.literal("local"),
45509
45726
  command: z.array(z.string()),
45510
45727
  environment: z.optional(z.record(z.string(), z.string())),
45511
45728
  enabled: z._default(z.boolean(), true),
45729
+ disabled: OpencodeMcpV2DisabledSchema,
45512
45730
  cwd: z.optional(z.string())
45513
45731
  });
45514
45732
  const OpencodeMcpRemoteServerSchema = z.looseObject({
45515
45733
  type: z.literal("remote"),
45516
45734
  url: z.string(),
45517
45735
  headers: z.optional(z.record(z.string(), z.string())),
45518
- enabled: z._default(z.boolean(), true)
45736
+ enabled: z._default(z.boolean(), true),
45737
+ disabled: OpencodeMcpV2DisabledSchema
45519
45738
  });
45520
45739
  const OPENCODE_PASSTHROUGH_SERVER_FIELDS = [
45521
45740
  "timeout",
@@ -45527,7 +45746,7 @@ const OPENCODE_MCP_TRANSPORT_KEYS = [
45527
45746
  ...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
45528
45747
  ...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
45529
45748
  ...OPENCODE_PASSTHROUGH_SERVER_FIELDS
45530
- ].filter((key) => key !== "enabled");
45749
+ ].filter((key) => key !== "enabled" && key !== "disabled");
45531
45750
  /**
45532
45751
  * A bare toggle entry: `{"enabled": <bool>}` with no transport of its own,
45533
45752
  * disabling a server another config layer defines. It is the third member of
@@ -45655,7 +45874,7 @@ const OPENCODE_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
45655
45874
  function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
45656
45875
  const dropped = Object.keys(serverConfig).filter((key) => !OPENCODE_TOGGLE_KEPT_KEYS.has(key));
45657
45876
  if (dropped.length === 0) return;
45658
- logger?.warn(`OpenCode MCP: "${serverName}" declares no transport, so it is written as a toggle entry and ${dropped.toSorted().join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
45877
+ logger?.warn(`OpenCode MCP: ${quoteValueForWarning(serverName)} declares no transport, so it is written as a toggle entry and ${dropped.toSorted().map(quoteValueForWarning).join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
45659
45878
  }
45660
45879
  /**
45661
45880
  * Convert standard MCP format to OpenCode native format
@@ -45735,9 +45954,35 @@ var OpencodeMcp = class extends ToolMcp {
45735
45954
  /** Directory layout; OpenCode forks (MiMo Code) override it. */
45736
45955
  static layout = OPENCODE_LAYOUT;
45737
45956
  json;
45738
- constructor(params) {
45957
+ /**
45958
+ * With a `logger`, the V2 values that lowering the content drops are warned
45959
+ * about (see {@link OpencodeMcp.lowerConfig}); `fromFile` passes one so the
45960
+ * warnings come from the lowering that builds the parsed view rather than
45961
+ * from an extra pass of their own. (`validate()`, which the parent
45962
+ * constructor calls before this view exists, lowers separately and silently.)
45963
+ */
45964
+ constructor({ logger, ...params }) {
45739
45965
  super(params);
45740
- this.json = OpencodeConfigSchema.parse(parse$1(this.fileContent || "{}"));
45966
+ this.json = OpencodeConfigSchema.parse(this.constructor.lowerConfig(parse$1(this.fileContent || "{}"), logger));
45967
+ }
45968
+ /**
45969
+ * Lowers the OpenCode V2 `mcp` spellings of a parsed config for a layout
45970
+ * that reads them (see {@link lowerOpencodeV2Mcp}), so every reader — import,
45971
+ * the toggle lookup on generate, and the rules feature's `instructions`
45972
+ * write to the same file — accepts a V2-shaped `mcp`. Only the parsed view
45973
+ * is lowered; content written back keeps whatever `mcp` a feature that does
45974
+ * not own the key found there. With a `logger`, each V2 value the lowering
45975
+ * drops is warned about.
45976
+ */
45977
+ static lowerConfig(json, logger) {
45978
+ if (!this.layout.readsV2ConfigSpellings || !isRecord$1(json) || json.mcp === void 0) return json;
45979
+ return {
45980
+ ...json,
45981
+ mcp: lowerOpencodeV2Mcp(json.mcp, (path, reason) => {
45982
+ const quoted = quoteValueForWarning(path);
45983
+ logger?.warn(reason === "conflict" ? `OpenCode MCP: ${quoted} conflicts with a V1 value that takes precedence (the flat "mcp" entry of the same name, or "enabled"), so it is dropped on import.` : `OpenCode MCP: ${quoted} has no Rulesync equivalent, so it is dropped on import.`);
45984
+ })
45985
+ };
45741
45986
  }
45742
45987
  getJson() {
45743
45988
  return this.json;
@@ -45754,7 +45999,7 @@ var OpencodeMcp = class extends ToolMcp {
45754
45999
  relativeFilePath: this.layout.jsonFileName
45755
46000
  };
45756
46001
  }
45757
- static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
46002
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false, logger }) {
45758
46003
  const basePaths = this.getSettablePaths({ global });
45759
46004
  const jsonDir = join(outputRoot, basePaths.relativeDirPath);
45760
46005
  let fileContent = null;
@@ -45776,7 +46021,8 @@ var OpencodeMcp = class extends ToolMcp {
45776
46021
  relativeDirPath: basePaths.relativeDirPath,
45777
46022
  relativeFilePath,
45778
46023
  fileContent: JSON.stringify(newJson, null, 2),
45779
- validate
46024
+ validate,
46025
+ logger
45780
46026
  });
45781
46027
  }
45782
46028
  static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
@@ -45795,7 +46041,12 @@ var OpencodeMcp = class extends ToolMcp {
45795
46041
  mcpServers: rulesyncMcp.getMcpServers(),
45796
46042
  replacement: "{env:$1}"
45797
46043
  });
45798
- const existingMcp = OpencodeConfigSchema.safeParse(parse$1(fileContent || "{}"));
46044
+ const existingJson = parse$1(fileContent || "{}");
46045
+ const existingMcp = OpencodeConfigSchema.safeParse(this.lowerConfig(existingJson));
46046
+ if (this.layout.readsV2ConfigSpellings && isRecord$1(existingJson)) {
46047
+ const containers = findOpencodeV2McpContainers(existingJson.mcp);
46048
+ 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.`);
46049
+ }
45799
46050
  const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
45800
46051
  return new this({
45801
46052
  outputRoot,
@@ -45895,7 +46146,7 @@ var OpencodeMcp = class extends ToolMcp {
45895
46146
  }
45896
46147
  validate() {
45897
46148
  const json = parseJsonc(this.fileContent || "{}");
45898
- const result = OpencodeConfigSchema.safeParse(json);
46149
+ const result = OpencodeConfigSchema.safeParse(this.constructor.lowerConfig(json));
45899
46150
  if (!result.success) return {
45900
46151
  success: false,
45901
46152
  error: result.error
@@ -55943,6 +56194,124 @@ function parseCodebuddySettings({ fileContent, relativePath }) {
55943
56194
  }
55944
56195
  }
55945
56196
  //#endregion
56197
+ //#region src/features/permissions/codewhale-project-config.ts
56198
+ /**
56199
+ * The top-level key of the per-project `.codewhale/config.toml` overlay that
56200
+ * hides Codewhale's shell tools. The overlay only tightens: `false` disables
56201
+ * shell for the session, while `true` is ignored with a warning.
56202
+ * @see https://github.com/Hmbown/Codewhale/blob/fead51eeea0fb6038ea3e30923751b1a1c765b25/docs/CONFIGURATION.md#per-project-overlay-485
56203
+ * @see https://github.com/Hmbown/Codewhale/blob/fead51eeea0fb6038ea3e30923751b1a1c765b25/crates/tui/src/lib.rs
56204
+ */
56205
+ const CODEWHALE_ALLOW_SHELL_KEY = "allow_shell";
56206
+ const CODEWHALE_PROJECT_CONFIG_PATHS = {
56207
+ relativeDirPath: CODEWHALE_DIR,
56208
+ relativeFilePath: CODEWHALE_CONFIG_FILE_NAME
56209
+ };
56210
+ /** The project `.codewhale/config.toml` overlay, as written by the permissions feature. */
56211
+ var CodewhaleProjectConfigFile = class extends ToolFile {
56212
+ validate() {
56213
+ return {
56214
+ success: true,
56215
+ error: null
56216
+ };
56217
+ }
56218
+ };
56219
+ /**
56220
+ * Whether the canonical config denies every shell command: a `deny` on the
56221
+ * catch-all `*` (or `**`) pattern of `bash` or of the all-tools `*` category.
56222
+ * Codewhale decides by action before specificity, so such a deny already
56223
+ * blocks every command in global scope too, whatever `allow` rules sit beside
56224
+ * it.
56225
+ */
56226
+ function deniesEveryShellCommand(permission) {
56227
+ return collectShellCommandRules(permission).rules.some(({ pattern, action }) => action === "deny" && isShellCatchAll(pattern));
56228
+ }
56229
+ /** Whether a canonical shell pattern names every command. */
56230
+ function isShellCatchAll(pattern) {
56231
+ const trimmed = pattern.trim();
56232
+ return trimmed === "*" || trimmed === "**";
56233
+ }
56234
+ function projectConfigFilePath(outputRoot) {
56235
+ return join(outputRoot, CODEWHALE_PROJECT_CONFIG_PATHS.relativeDirPath, CODEWHALE_PROJECT_CONFIG_PATHS.relativeFilePath);
56236
+ }
56237
+ function parseProjectConfig({ fileContent, filePath }) {
56238
+ return parseSharedConfig({
56239
+ format: "toml",
56240
+ fileContent,
56241
+ filePath,
56242
+ invalidRootPolicy: "error"
56243
+ });
56244
+ }
56245
+ /**
56246
+ * Whether an overlay disables shell (`allow_shell = false`). A file that
56247
+ * cannot be read as TOML is reported and treated as not disabling shell.
56248
+ */
56249
+ function disablesShell({ fileContent, filePath, logger }) {
56250
+ try {
56251
+ return parseProjectConfig({
56252
+ fileContent,
56253
+ filePath
56254
+ })[CODEWHALE_ALLOW_SHELL_KEY] === false;
56255
+ } catch (error) {
56256
+ logger?.warn(`Codewhale permissions: not reading "allow_shell" from ${filePath}, so a shell deny set there is not taken into account: ${formatError(error)}`);
56257
+ return false;
56258
+ }
56259
+ }
56260
+ /**
56261
+ * Whether the project overlay at `outputRoot` disables shell
56262
+ * (`allow_shell = false`), for import.
56263
+ */
56264
+ async function readCodewhaleProjectShellDisabled({ outputRoot, logger }) {
56265
+ const filePath = projectConfigFilePath(outputRoot);
56266
+ const fileContent = await readFileContentOrNull(filePath);
56267
+ if (fileContent === null) return false;
56268
+ return disablesShell({
56269
+ fileContent,
56270
+ filePath,
56271
+ logger
56272
+ });
56273
+ }
56274
+ /**
56275
+ * The project `.codewhale/config.toml` overlay carrying `allow_shell = false`
56276
+ * when the canonical config denies every shell command, or `null` when the
56277
+ * file needs no change.
56278
+ *
56279
+ * Only `allow_shell` is written; every other overlay key (`model`,
56280
+ * `approval_policy`, ...) is kept, and an existing file that is not valid TOML
56281
+ * is an error only when it has to be written. An existing
56282
+ * `allow_shell = false` is never removed: the overlay cannot widen access, and
56283
+ * a hand-written value cannot be told apart from one rulesync wrote, so it is
56284
+ * reported instead.
56285
+ */
56286
+ async function createCodewhaleProjectConfigFile({ outputRoot = process.cwd(), config, logger }) {
56287
+ const filePath = projectConfigFilePath(outputRoot);
56288
+ const existingContent = await readFileContentOrNull(filePath);
56289
+ if (!deniesEveryShellCommand(config.permission)) {
56290
+ if (existingContent !== null && disablesShell({
56291
+ fileContent: existingContent,
56292
+ filePath,
56293
+ logger
56294
+ })) logger?.warn(`Codewhale permissions: keeping "allow_shell = false" in ${filePath}; rulesync does not remove it. Delete it by hand to re-enable shell tools in this project, or add bash: { "*": "deny" } to the rulesync permissions to match it.`);
56295
+ return null;
56296
+ }
56297
+ if (parseProjectConfig({
56298
+ fileContent: existingContent ?? "",
56299
+ filePath
56300
+ })[CODEWHALE_ALLOW_SHELL_KEY] === false) return null;
56301
+ return new CodewhaleProjectConfigFile({
56302
+ outputRoot,
56303
+ ...CODEWHALE_PROJECT_CONFIG_PATHS,
56304
+ fileContent: applySharedConfigPatch({
56305
+ fileKey: sharedConfigFileKey(CODEWHALE_PROJECT_CONFIG_PATHS),
56306
+ feature: "permissions",
56307
+ existingContent: existingContent ?? "",
56308
+ patch: { [CODEWHALE_ALLOW_SHELL_KEY]: false },
56309
+ filePath,
56310
+ logger
56311
+ })
56312
+ });
56313
+ }
56314
+ //#endregion
55946
56315
  //#region src/features/permissions/codewhale-repo-law.ts
55947
56316
  /**
55948
56317
  * Project-scope Codewhale permissions: the enforced `protected_invariants` of
@@ -56093,7 +56462,8 @@ function collectRestrictions({ config, logger }) {
56093
56462
  let skippedAllowCount = 0;
56094
56463
  for (const [category, rules] of Object.entries(config.permission)) {
56095
56464
  if (!REPO_LAW_CATEGORIES.includes(category)) {
56096
- if (Object.keys(rules).length > 0) skippedCategories.push(category);
56465
+ const entries = Object.entries(rules);
56466
+ if ((category === "bash" && entries.some(([pattern, action]) => action === "deny" && isShellCatchAll(pattern)) ? entries.filter(([, action]) => action === "allow") : entries).length > 0) skippedCategories.push(category);
56097
56467
  continue;
56098
56468
  }
56099
56469
  for (const [pattern, action] of Object.entries(rules)) {
@@ -56819,17 +57189,32 @@ async function warnOnShadowedConstitution({ outputRoot, logger }) {
56819
57189
  * Project scope manages the enforced `protected_invariants` of the repo
56820
57190
  * constitution `.codewhale/constitution.json` (see `codewhale-repo-law.ts`):
56821
57191
  * the project `.codewhale/config.toml` overlay cannot carry rules, but repo
56822
- * law can hold writes to protected paths.
57192
+ * law can hold writes to protected paths. A deny on every shell command also
57193
+ * sets `allow_shell = false` in that overlay (see
57194
+ * `codewhale-project-config.ts`), which import reads back here.
56823
57195
  *
56824
57196
  * @see https://github.com/Hmbown/Codewhale/blob/main/docs/CONFIGURATION.md
56825
57197
  * @see https://github.com/Hmbown/Codewhale/blob/main/docs/AUTHORIZATION_ORDER.md
56826
57198
  */
56827
57199
  var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
56828
- constructor(params) {
57200
+ /**
57201
+ * Whether the project `.codewhale/config.toml` overlay disables shell
57202
+ * (`allow_shell = false`); read on import in project scope only.
57203
+ */
57204
+ projectShellDisabled;
57205
+ constructor({ projectShellDisabled = false, ...params }) {
56829
57206
  super({
56830
57207
  ...params,
56831
57208
  fileContent: params.fileContent ?? ""
56832
57209
  });
57210
+ this.projectShellDisabled = projectShellDisabled;
57211
+ }
57212
+ /**
57213
+ * The project `.codewhale/config.toml` overlay, where a deny on every shell
57214
+ * command becomes `allow_shell = false` beside the user's other overlay keys.
57215
+ */
57216
+ static getExtraSharedWritePaths(options) {
57217
+ return options?.global ? [] : [{ ...CODEWHALE_PROJECT_CONFIG_PATHS }];
56833
57218
  }
56834
57219
  /**
56835
57220
  * Codewhale's approval card writes to the global file, and the constitution
@@ -56866,7 +57251,11 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
56866
57251
  relativeFilePath: paths.relativeFilePath,
56867
57252
  fileContent,
56868
57253
  validate,
56869
- global
57254
+ global,
57255
+ projectShellDisabled: global ? false : await readCodewhaleProjectShellDisabled({
57256
+ outputRoot,
57257
+ logger
57258
+ })
56870
57259
  });
56871
57260
  }
56872
57261
  static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
@@ -56928,11 +57317,15 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
56928
57317
  toRulesyncPermissions() {
56929
57318
  const filePath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
56930
57319
  if (!this.global) {
56931
- const document = parseCodewhaleConstitution({
57320
+ const permission = codewhaleConstitutionToCanonical(parseCodewhaleConstitution({
56932
57321
  fileContent: this.getFileContent(),
56933
57322
  filePath
56934
- });
56935
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission: codewhaleConstitutionToCanonical(document) }, null, 2) });
57323
+ }));
57324
+ if (this.projectShellDisabled) permission.bash = {
57325
+ ...permission.bash,
57326
+ "*": "deny"
57327
+ };
57328
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
56936
57329
  }
56937
57330
  const { permission, nativeRules } = codewhaleRulesToCanonical(partitionRuleRecords(parsePermissionsDocument({
56938
57331
  fileContent: this.getFileContent(),
@@ -71861,6 +72254,14 @@ var PermissionsProcessor = class extends FeatureProcessor {
71861
72254
  logger: this.logger,
71862
72255
  global: this.global
71863
72256
  });
72257
+ if (this.toolTarget === "codewhale" && !this.global) {
72258
+ const projectConfigFile = await createCodewhaleProjectConfigFile({
72259
+ outputRoot: this.outputRoot,
72260
+ config: effectivePermissions.getJson(),
72261
+ logger: this.logger
72262
+ });
72263
+ return projectConfigFile ? [toolPermissions, projectConfigFile] : [toolPermissions];
72264
+ }
71864
72265
  if (this.toolTarget !== "codexcli") return [toolPermissions];
71865
72266
  return [toolPermissions, createCodexcliBashRulesFile({
71866
72267
  outputRoot: this.outputRoot,
@@ -101527,4 +101928,4 @@ async function importChecksCore(params) {
101527
101928
  //#endregion
101528
101929
  export { RulesyncHooks as $, removeFile as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_RELATIVE_DIR_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, DEPRECATED_FEATURE_REPLACEMENTS as Bn, getFileSize as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_MCP_SCHEMA_URL as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_USER_CONFIG_DIR_NAME as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, quoteForLog as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, truncateText as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as In, directoryExists as It, RulesyncRule as J, stripHiddenCharacters as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, stripControlCharacters as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, parseCommaSeparatedList as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectoryStrict as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, ALL_FEATURES as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_MCP_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, hasDeceptiveHiddenCharacters as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, formatError as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, hasEnclosingMarkOutsideKeycap as Wn, isSymlink as Wt, RulesyncPermissions as X, readFileContentOrNull as Xt, RulesyncRuleFrontmatterSchema as Y, readFileContent as Yt, RulesyncMcp as Z, removeDirectory as Zt, LETTACODE_DIR as _, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, toPosixPath as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, ALL_TOOL_TARGETS as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ToolTargetSchema as dn, ConfigResolver as dt, removeFileStrict as en, getRulesyncSourceCandidates as et, displayWidthOf as f, CURATED_RULES_FEATURE_SUBDIR as fn, mergeInputRootConfigs as ft, HooksProcessor as g, RULESYNC_CHECKS_RELATIVE_DIR_PATH as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as hn, ConfigFileSchema as ht, inspectInputRoots as i, splitPathSegments as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_RULES_RELATIVE_DIR_PATH as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_PERMISSIONS_SCHEMA_URL as kn, ErrorCodes as kt, SubagentsProcessor as l, ALL_TOOL_TARGETS_WITH_WILDCARD as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, MAX_FILE_SIZE as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, resolvePath as nn, parseJsonc as nt, convertFromTool as o, writeFileBuffer as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, FEATURE_SOURCE_TREE_ENTRIES as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, stripControlCharactersKeepingLineFeeds as qn, listSubdirectoryNames as qt, generate as r, runWithDirectoryRollback as rn, RulesyncCommand as rt, isPackagingToolTarget as s, writeFileContent as sn, stringifyFrontmatter as st, importFromTool as t, removeTempDirectory as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, PACKAGING_TOOL_TARGETS as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_CONFIG_RELATIVE_FILE_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_CONFIG_SCHEMA_URL as yn, findControlCharacter as yt, QWENCODE_DIR as z, ALL_FEATURES_WITH_WILDCARD as zn, findFilesByGlobs as zt };
101529
101930
 
101530
- //# sourceMappingURL=import-ORYbMDJK.js.map
101931
+ //# sourceMappingURL=import-5-Uennl1.js.map