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.
- package/dist/cli/index.cjs +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CBvXTNp8.cjs → import-Bmb7ofrF.cjs} +253 -48
- package/dist/{import-ORYbMDJK.js → import-DgKWnDni.js} +237 -32
- package/dist/{import-ORYbMDJK.js.map → import-DgKWnDni.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
4827
|
-
*
|
|
4828
|
-
* `
|
|
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/
|
|
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
|
|
6265
|
-
* budget, and fails a `tool_call`
|
|
6266
|
-
* `extensionHandlers.toolCallTimeoutMs`
|
|
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: {
|
|
12937
|
-
|
|
12938
|
-
|
|
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 ||
|
|
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 (
|
|
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
|
|
33102
|
-
* agent to continue once with the
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
57124
|
+
const permission = codewhaleConstitutionToCanonical(parseCodewhaleConstitution({
|
|
56932
57125
|
fileContent: this.getFileContent(),
|
|
56933
57126
|
filePath
|
|
56934
|
-
});
|
|
56935
|
-
|
|
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-
|
|
101735
|
+
//# sourceMappingURL=import-DgKWnDni.js.map
|