rulesync 31.0.0 → 32.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",
@@ -32835,6 +32842,31 @@ const PI_PROMPT_BLOCKING_EVENT = "input";
32835
32842
  * @see https://github.com/earendil-works/pi/blob/v0.87.1/packages/coding-agent/src/core/extensions/types.ts
32836
32843
  */
32837
32844
  const PI_SETTLE_BLOCKING_EVENT = "agent_before_settle";
32845
+ /**
32846
+ * `session_stop` is oh-my-pi's stop surface (it has no `agent_before_settle`).
32847
+ * It fires when a main-session run is about to settle (never for subagents,
32848
+ * nor for a run the user aborted), and a handler returning
32849
+ * `{ continue: true, additionalContext }` queues one more turn with that text
32850
+ * as hidden model-visible context. As on Pi, only a stop command exiting with
32851
+ * code 2 asks for that, the feedback of every such command is combined, and
32852
+ * any other failure is rethrown for oh-my-pi to report. The generated handler
32853
+ * continues only when `event.stop_hook_active` is false, which oh-my-pi sets
32854
+ * after a continuation and clears once a stop pass lets the run settle, so the
32855
+ * agent is continued at most once per stop (the flag is shared by every
32856
+ * extension, so another extension's continuation also suppresses ours);
32857
+ * oh-my-pi additionally caps these
32858
+ * advisory continuations at 8 in a row. A run that ended on a provider error
32859
+ * (`stopReason: "error"`) is not continued, matching Pi's `completed`-only
32860
+ * gate. The advisory form is used rather than `{ decision: "block", reason }`,
32861
+ * which oh-my-pi keeps enforcing without that cap. oh-my-pi gives
32862
+ * `session_stop` handlers the 30-second extension handler budget: stop
32863
+ * commands running longer time the handler out, the run settles without a
32864
+ * continuation, and the command is not killed.
32865
+ *
32866
+ * @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/extensions.md
32867
+ * @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/packages/coding-agent/src/session/agent-session.ts
32868
+ */
32869
+ const OMP_SESSION_STOP_BLOCKING_EVENT = "session_stop";
32838
32870
  const PI_EXTENSION_DIALECT = {
32839
32871
  overrideKey: "pi",
32840
32872
  typesPackage: "@earendil-works/pi-coding-agent",
@@ -32856,7 +32888,8 @@ const OMP_EXTENSION_DIALECT = {
32856
32888
  const PI_BLOCKING_MODE_BY_EVENT = {
32857
32889
  [PI_BLOCKING_EVENT]: "tool",
32858
32890
  [PI_PROMPT_BLOCKING_EVENT]: "prompt",
32859
- [PI_SETTLE_BLOCKING_EVENT]: "settle"
32891
+ [PI_SETTLE_BLOCKING_EVENT]: "settle",
32892
+ [OMP_SESSION_STOP_BLOCKING_EVENT]: "sessionStop"
32860
32893
  };
32861
32894
  /**
32862
32895
  * The body emitted inside a blocking handler's `catch`, per gate. `tool_call`
@@ -32869,7 +32902,8 @@ function failureLinesByMode(dialect) {
32869
32902
  none: [],
32870
32903
  tool: ["return { block: true, reason: toBlockReason(error) };"],
32871
32904
  prompt: ["reportPromptGateFailure(ctx, toBlockReason(error));", dialect.promptCancelLine],
32872
- settle: ["stopHookFailures.push(error);"]
32905
+ settle: ["stopHookFailures.push(error);"],
32906
+ sessionStop: ["stopHookFailures.push(error);"]
32873
32907
  };
32874
32908
  }
32875
32909
  /**
@@ -32961,6 +32995,12 @@ const PROMPT_GATE_HELPER_LINES = [
32961
32995
  " console.error(reason);",
32962
32996
  "}"
32963
32997
  ];
32998
+ /** Helper emitted alongside every stop gate (Pi's settle gate and oh-my-pi's `session_stop`). */
32999
+ const STOP_HOOK_BLOCK_HELPER_LINES = [
33000
+ "function isStopHookBlock(error: unknown): boolean {",
33001
+ " return (error as { code?: unknown } | null)?.code === 2;",
33002
+ "}"
33003
+ ];
32964
33004
  /**
32965
33005
  * Helper emitted alongside the stop hook's settle gate. `event.entries` holds
32966
33006
  * the drafts earlier extensions proposed at this boundary, so they are kept and
@@ -32971,10 +33011,6 @@ const PROMPT_GATE_HELPER_LINES = [
32971
33011
  * appended message is committed, and Pi re-validates the final context itself.
32972
33012
  */
32973
33013
  const STOP_HOOK_HELPER_LINES = [
32974
- "function isStopHookBlock(error: unknown): boolean {",
32975
- " return (error as { code?: unknown } | null)?.code === 2;",
32976
- "}",
32977
- "",
32978
33014
  "function continueAfterStopHook(",
32979
33015
  " event: AgentBeforeSettleEvent,",
32980
33016
  " reason: string,",
@@ -33057,14 +33093,16 @@ function buildSubscriptionLines(handlerGroups, dialect) {
33057
33093
  const blocking = PI_BLOCKING_MODE_BY_EVENT[piEvent] ?? "none";
33058
33094
  const isPromptGate = blocking === "prompt";
33059
33095
  const isSettleGate = blocking === "settle";
33096
+ const isSessionStopGate = blocking === "sessionStop";
33097
+ const collectsStopFailures = isSettleGate || isSessionStopGate;
33060
33098
  const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
33061
33099
  const usesErrorFlag = handlers.some((h) => h.onlyOnError);
33062
33100
  const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
33063
- const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate || isSettleGate ? "event" : "";
33101
+ const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate || collectsStopFailures ? "event" : "";
33064
33102
  lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${params}) => {`);
33065
33103
  if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
33066
33104
  if (isPromptGate) lines.push(` if (event.source === "extension") ${dialect.promptPassLine}`);
33067
- if (isSettleGate) lines.push(" const stopHookFailures: unknown[] = [];");
33105
+ if (collectsStopFailures) lines.push(" const stopHookFailures: unknown[] = [];");
33068
33106
  for (const handler of handlers) lines.push(...buildCommandLines({
33069
33107
  handler,
33070
33108
  usesToolName,
@@ -33080,6 +33118,17 @@ function buildSubscriptionLines(handlerGroups, dialect) {
33080
33118
  lines.push(" }");
33081
33119
  lines.push(" if (stopHookFailures.length > 0) throw stopHookFailures[0];");
33082
33120
  }
33121
+ if (isSessionStopGate) {
33122
+ lines.push(" const blocks = stopHookFailures.filter(isStopHookBlock);");
33123
+ lines.push(" const endedOnError = (event.last_assistant_message as { stopReason?: unknown } | undefined)?.stopReason === \"error\";");
33124
+ lines.push(" if (blocks.length > 0 && !event.stop_hook_active && !endedOnError) {");
33125
+ lines.push(" return {");
33126
+ lines.push(" continue: true,");
33127
+ lines.push(" additionalContext: `Stop hook feedback:\\n${blocks.map(toBlockReason).join(\"\\n\\n\")}`,");
33128
+ lines.push(" };");
33129
+ lines.push(" }");
33130
+ lines.push(" if (stopHookFailures.length > 0) throw stopHookFailures[0];");
33131
+ }
33083
33132
  lines.push(" });");
33084
33133
  if (isSettleGate) {
33085
33134
  lines.push(` pi.on("agent_settled", async () => {`);
@@ -33098,8 +33147,9 @@ function buildSubscriptionLines(handlerGroups, dialect) {
33098
33147
  * `{ block: true, reason }`, and on `input` — Pi's prompt-submission gate —
33099
33148
  * where a non-zero exit cancels the prompt with the dialect's cancel result
33100
33149
  * (`{ 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.
33150
+ * on `agent_before_settle` (Pi) / `session_stop` (oh-my-pi), where a stop
33151
+ * command exiting with code 2 asks the agent to continue once with the
33152
+ * command's output as feedback.
33103
33153
  * `postToolUse` and `postToolUseFailure` share Pi's `tool_result` event; the
33104
33154
  * latter's commands run only when `event.isError` is set.
33105
33155
  *
@@ -33120,7 +33170,8 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
33120
33170
  const subscriptionLines = buildSubscriptionLines(handlerGroups, dialect);
33121
33171
  const hasPromptGate = Boolean(handlerGroups[PI_PROMPT_BLOCKING_EVENT]);
33122
33172
  const hasSettleGate = Boolean(handlerGroups[PI_SETTLE_BLOCKING_EVENT]);
33123
- const needsBlockReasonHelper = Boolean(handlerGroups[PI_BLOCKING_EVENT]) || hasPromptGate || hasSettleGate;
33173
+ const hasSessionStopGate = Boolean(handlerGroups[OMP_SESSION_STOP_BLOCKING_EVENT]);
33174
+ const needsBlockReasonHelper = Boolean(handlerGroups[PI_BLOCKING_EVENT]) || hasPromptGate || hasSettleGate || hasSessionStopGate;
33124
33175
  const lines = ["// Generated by rulesync. Do not edit manually."];
33125
33176
  if (subscriptionLines.length === 0) {
33126
33177
  lines.push("export default function () {}");
@@ -33147,6 +33198,10 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
33147
33198
  lines.push(...PROMPT_GATE_HELPER_LINES);
33148
33199
  lines.push("");
33149
33200
  }
33201
+ if (hasSettleGate || hasSessionStopGate) {
33202
+ lines.push(...STOP_HOOK_BLOCK_HELPER_LINES);
33203
+ lines.push("");
33204
+ }
33150
33205
  if (hasSettleGate) {
33151
33206
  lines.push(...STOP_HOOK_HELPER_LINES);
33152
33207
  lines.push("");
@@ -55943,6 +55998,124 @@ function parseCodebuddySettings({ fileContent, relativePath }) {
55943
55998
  }
55944
55999
  }
55945
56000
  //#endregion
56001
+ //#region src/features/permissions/codewhale-project-config.ts
56002
+ /**
56003
+ * The top-level key of the per-project `.codewhale/config.toml` overlay that
56004
+ * hides Codewhale's shell tools. The overlay only tightens: `false` disables
56005
+ * shell for the session, while `true` is ignored with a warning.
56006
+ * @see https://github.com/Hmbown/Codewhale/blob/fead51eeea0fb6038ea3e30923751b1a1c765b25/docs/CONFIGURATION.md#per-project-overlay-485
56007
+ * @see https://github.com/Hmbown/Codewhale/blob/fead51eeea0fb6038ea3e30923751b1a1c765b25/crates/tui/src/lib.rs
56008
+ */
56009
+ const CODEWHALE_ALLOW_SHELL_KEY = "allow_shell";
56010
+ const CODEWHALE_PROJECT_CONFIG_PATHS = {
56011
+ relativeDirPath: CODEWHALE_DIR,
56012
+ relativeFilePath: CODEWHALE_CONFIG_FILE_NAME
56013
+ };
56014
+ /** The project `.codewhale/config.toml` overlay, as written by the permissions feature. */
56015
+ var CodewhaleProjectConfigFile = class extends ToolFile {
56016
+ validate() {
56017
+ return {
56018
+ success: true,
56019
+ error: null
56020
+ };
56021
+ }
56022
+ };
56023
+ /**
56024
+ * Whether the canonical config denies every shell command: a `deny` on the
56025
+ * catch-all `*` (or `**`) pattern of `bash` or of the all-tools `*` category.
56026
+ * Codewhale decides by action before specificity, so such a deny already
56027
+ * blocks every command in global scope too, whatever `allow` rules sit beside
56028
+ * it.
56029
+ */
56030
+ function deniesEveryShellCommand(permission) {
56031
+ return collectShellCommandRules(permission).rules.some(({ pattern, action }) => action === "deny" && isShellCatchAll(pattern));
56032
+ }
56033
+ /** Whether a canonical shell pattern names every command. */
56034
+ function isShellCatchAll(pattern) {
56035
+ const trimmed = pattern.trim();
56036
+ return trimmed === "*" || trimmed === "**";
56037
+ }
56038
+ function projectConfigFilePath(outputRoot) {
56039
+ return join(outputRoot, CODEWHALE_PROJECT_CONFIG_PATHS.relativeDirPath, CODEWHALE_PROJECT_CONFIG_PATHS.relativeFilePath);
56040
+ }
56041
+ function parseProjectConfig({ fileContent, filePath }) {
56042
+ return parseSharedConfig({
56043
+ format: "toml",
56044
+ fileContent,
56045
+ filePath,
56046
+ invalidRootPolicy: "error"
56047
+ });
56048
+ }
56049
+ /**
56050
+ * Whether an overlay disables shell (`allow_shell = false`). A file that
56051
+ * cannot be read as TOML is reported and treated as not disabling shell.
56052
+ */
56053
+ function disablesShell({ fileContent, filePath, logger }) {
56054
+ try {
56055
+ return parseProjectConfig({
56056
+ fileContent,
56057
+ filePath
56058
+ })[CODEWHALE_ALLOW_SHELL_KEY] === false;
56059
+ } catch (error) {
56060
+ logger?.warn(`Codewhale permissions: not reading "allow_shell" from ${filePath}, so a shell deny set there is not taken into account: ${formatError(error)}`);
56061
+ return false;
56062
+ }
56063
+ }
56064
+ /**
56065
+ * Whether the project overlay at `outputRoot` disables shell
56066
+ * (`allow_shell = false`), for import.
56067
+ */
56068
+ async function readCodewhaleProjectShellDisabled({ outputRoot, logger }) {
56069
+ const filePath = projectConfigFilePath(outputRoot);
56070
+ const fileContent = await readFileContentOrNull(filePath);
56071
+ if (fileContent === null) return false;
56072
+ return disablesShell({
56073
+ fileContent,
56074
+ filePath,
56075
+ logger
56076
+ });
56077
+ }
56078
+ /**
56079
+ * The project `.codewhale/config.toml` overlay carrying `allow_shell = false`
56080
+ * when the canonical config denies every shell command, or `null` when the
56081
+ * file needs no change.
56082
+ *
56083
+ * Only `allow_shell` is written; every other overlay key (`model`,
56084
+ * `approval_policy`, ...) is kept, and an existing file that is not valid TOML
56085
+ * is an error only when it has to be written. An existing
56086
+ * `allow_shell = false` is never removed: the overlay cannot widen access, and
56087
+ * a hand-written value cannot be told apart from one rulesync wrote, so it is
56088
+ * reported instead.
56089
+ */
56090
+ async function createCodewhaleProjectConfigFile({ outputRoot = process.cwd(), config, logger }) {
56091
+ const filePath = projectConfigFilePath(outputRoot);
56092
+ const existingContent = await readFileContentOrNull(filePath);
56093
+ if (!deniesEveryShellCommand(config.permission)) {
56094
+ if (existingContent !== null && disablesShell({
56095
+ fileContent: existingContent,
56096
+ filePath,
56097
+ logger
56098
+ })) 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.`);
56099
+ return null;
56100
+ }
56101
+ if (parseProjectConfig({
56102
+ fileContent: existingContent ?? "",
56103
+ filePath
56104
+ })[CODEWHALE_ALLOW_SHELL_KEY] === false) return null;
56105
+ return new CodewhaleProjectConfigFile({
56106
+ outputRoot,
56107
+ ...CODEWHALE_PROJECT_CONFIG_PATHS,
56108
+ fileContent: applySharedConfigPatch({
56109
+ fileKey: sharedConfigFileKey(CODEWHALE_PROJECT_CONFIG_PATHS),
56110
+ feature: "permissions",
56111
+ existingContent: existingContent ?? "",
56112
+ patch: { [CODEWHALE_ALLOW_SHELL_KEY]: false },
56113
+ filePath,
56114
+ logger
56115
+ })
56116
+ });
56117
+ }
56118
+ //#endregion
55946
56119
  //#region src/features/permissions/codewhale-repo-law.ts
55947
56120
  /**
55948
56121
  * Project-scope Codewhale permissions: the enforced `protected_invariants` of
@@ -56093,7 +56266,8 @@ function collectRestrictions({ config, logger }) {
56093
56266
  let skippedAllowCount = 0;
56094
56267
  for (const [category, rules] of Object.entries(config.permission)) {
56095
56268
  if (!REPO_LAW_CATEGORIES.includes(category)) {
56096
- if (Object.keys(rules).length > 0) skippedCategories.push(category);
56269
+ const entries = Object.entries(rules);
56270
+ if ((category === "bash" && entries.some(([pattern, action]) => action === "deny" && isShellCatchAll(pattern)) ? entries.filter(([, action]) => action === "allow") : entries).length > 0) skippedCategories.push(category);
56097
56271
  continue;
56098
56272
  }
56099
56273
  for (const [pattern, action] of Object.entries(rules)) {
@@ -56819,17 +56993,32 @@ async function warnOnShadowedConstitution({ outputRoot, logger }) {
56819
56993
  * Project scope manages the enforced `protected_invariants` of the repo
56820
56994
  * constitution `.codewhale/constitution.json` (see `codewhale-repo-law.ts`):
56821
56995
  * the project `.codewhale/config.toml` overlay cannot carry rules, but repo
56822
- * law can hold writes to protected paths.
56996
+ * law can hold writes to protected paths. A deny on every shell command also
56997
+ * sets `allow_shell = false` in that overlay (see
56998
+ * `codewhale-project-config.ts`), which import reads back here.
56823
56999
  *
56824
57000
  * @see https://github.com/Hmbown/Codewhale/blob/main/docs/CONFIGURATION.md
56825
57001
  * @see https://github.com/Hmbown/Codewhale/blob/main/docs/AUTHORIZATION_ORDER.md
56826
57002
  */
56827
57003
  var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
56828
- constructor(params) {
57004
+ /**
57005
+ * Whether the project `.codewhale/config.toml` overlay disables shell
57006
+ * (`allow_shell = false`); read on import in project scope only.
57007
+ */
57008
+ projectShellDisabled;
57009
+ constructor({ projectShellDisabled = false, ...params }) {
56829
57010
  super({
56830
57011
  ...params,
56831
57012
  fileContent: params.fileContent ?? ""
56832
57013
  });
57014
+ this.projectShellDisabled = projectShellDisabled;
57015
+ }
57016
+ /**
57017
+ * The project `.codewhale/config.toml` overlay, where a deny on every shell
57018
+ * command becomes `allow_shell = false` beside the user's other overlay keys.
57019
+ */
57020
+ static getExtraSharedWritePaths(options) {
57021
+ return options?.global ? [] : [{ ...CODEWHALE_PROJECT_CONFIG_PATHS }];
56833
57022
  }
56834
57023
  /**
56835
57024
  * Codewhale's approval card writes to the global file, and the constitution
@@ -56866,7 +57055,11 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
56866
57055
  relativeFilePath: paths.relativeFilePath,
56867
57056
  fileContent,
56868
57057
  validate,
56869
- global
57058
+ global,
57059
+ projectShellDisabled: global ? false : await readCodewhaleProjectShellDisabled({
57060
+ outputRoot,
57061
+ logger
57062
+ })
56870
57063
  });
56871
57064
  }
56872
57065
  static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
@@ -56928,11 +57121,15 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
56928
57121
  toRulesyncPermissions() {
56929
57122
  const filePath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
56930
57123
  if (!this.global) {
56931
- const document = parseCodewhaleConstitution({
57124
+ const permission = codewhaleConstitutionToCanonical(parseCodewhaleConstitution({
56932
57125
  fileContent: this.getFileContent(),
56933
57126
  filePath
56934
- });
56935
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission: codewhaleConstitutionToCanonical(document) }, null, 2) });
57127
+ }));
57128
+ if (this.projectShellDisabled) permission.bash = {
57129
+ ...permission.bash,
57130
+ "*": "deny"
57131
+ };
57132
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
56936
57133
  }
56937
57134
  const { permission, nativeRules } = codewhaleRulesToCanonical(partitionRuleRecords(parsePermissionsDocument({
56938
57135
  fileContent: this.getFileContent(),
@@ -71861,6 +72058,14 @@ var PermissionsProcessor = class extends FeatureProcessor {
71861
72058
  logger: this.logger,
71862
72059
  global: this.global
71863
72060
  });
72061
+ if (this.toolTarget === "codewhale" && !this.global) {
72062
+ const projectConfigFile = await createCodewhaleProjectConfigFile({
72063
+ outputRoot: this.outputRoot,
72064
+ config: effectivePermissions.getJson(),
72065
+ logger: this.logger
72066
+ });
72067
+ return projectConfigFile ? [toolPermissions, projectConfigFile] : [toolPermissions];
72068
+ }
71864
72069
  if (this.toolTarget !== "codexcli") return [toolPermissions];
71865
72070
  return [toolPermissions, createCodexcliBashRulesFile({
71866
72071
  outputRoot: this.outputRoot,
@@ -101527,4 +101732,4 @@ async function importChecksCore(params) {
101527
101732
  //#endregion
101528
101733
  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
101734
 
101530
- //# sourceMappingURL=import-ORYbMDJK.js.map
101735
+ //# sourceMappingURL=import-DgKWnDni.js.map