rulesync 30.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/README.md +1 -1
- package/dist/cli/index.cjs +4 -4
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-XIYCKmjg.cjs → import-Bmb7ofrF.cjs} +493 -90
- package/dist/{import-DBd2G6Eo.js → import-DgKWnDni.js} +477 -74
- package/dist/{import-DBd2G6Eo.js.map → import-DgKWnDni.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -913,6 +913,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
913
913
|
"cursor",
|
|
914
914
|
"factorydroid",
|
|
915
915
|
"gitlabduo",
|
|
916
|
+
"goose",
|
|
916
917
|
"hermesagent",
|
|
917
918
|
"kilo",
|
|
918
919
|
"qwencode",
|
|
@@ -4822,13 +4823,11 @@ const PI_HOOK_EVENTS = [
|
|
|
4822
4823
|
/**
|
|
4823
4824
|
* Hook events supported by oh-my-pi (`omp`), Pi's fork, through the same
|
|
4824
4825
|
* generated TypeScript extension. oh-my-pi keeps Pi's names for every event
|
|
4825
|
-
* listed here
|
|
4826
|
-
*
|
|
4827
|
-
* `
|
|
4828
|
-
* result; it needs its own handler in the generator and is left for a
|
|
4829
|
-
* 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.
|
|
4830
4829
|
*
|
|
4831
|
-
* @see https://github.com/can1357/oh-my-pi/blob/
|
|
4830
|
+
* @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/extensions.md
|
|
4832
4831
|
*/
|
|
4833
4832
|
const OMP_HOOK_EVENTS = [
|
|
4834
4833
|
"sessionStart",
|
|
@@ -4839,6 +4838,7 @@ const OMP_HOOK_EVENTS = [
|
|
|
4839
4838
|
"preModelInvocation",
|
|
4840
4839
|
"postModelInvocation",
|
|
4841
4840
|
"beforeSubmitPrompt",
|
|
4841
|
+
"stop",
|
|
4842
4842
|
"preCompact",
|
|
4843
4843
|
"postCompact"
|
|
4844
4844
|
];
|
|
@@ -6260,9 +6260,10 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
|
|
|
6260
6260
|
/**
|
|
6261
6261
|
* Map canonical camelCase event names to oh-my-pi extension event names. They
|
|
6262
6262
|
* are Pi's names (see CANONICAL_TO_PI_EVENT_NAMES) for the events oh-my-pi
|
|
6263
|
-
* kept
|
|
6264
|
-
* budget, and fails a `tool_call`
|
|
6265
|
-
* `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).
|
|
6266
6267
|
*
|
|
6267
6268
|
* @see https://github.com/can1357/oh-my-pi/blob/40e9368ef0458fd9073329cdff4174895f91bc6b/docs/extensions.md
|
|
6268
6269
|
*/
|
|
@@ -6275,6 +6276,7 @@ const CANONICAL_TO_OMP_EVENT_NAMES = {
|
|
|
6275
6276
|
preModelInvocation: "context",
|
|
6276
6277
|
postModelInvocation: "message_end",
|
|
6277
6278
|
beforeSubmitPrompt: "input",
|
|
6279
|
+
stop: "session_stop",
|
|
6278
6280
|
preCompact: "session_before_compact",
|
|
6279
6281
|
postCompact: "session_compact"
|
|
6280
6282
|
};
|
|
@@ -12932,10 +12934,16 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12932
12934
|
[CODEWHALE_CONFIG_SHARED_FILE_KEY]: {
|
|
12933
12935
|
format: "toml",
|
|
12934
12936
|
invalidRootPolicy: "error",
|
|
12935
|
-
features: {
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
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
|
+
}
|
|
12939
12947
|
},
|
|
12940
12948
|
".letta/settings.json": {
|
|
12941
12949
|
format: "json",
|
|
@@ -14199,12 +14207,12 @@ const AmpCheckFrontmatterSchema = z.looseObject({
|
|
|
14199
14207
|
* this format continue to apply.
|
|
14200
14208
|
* @see https://ampcode.com/docs/review
|
|
14201
14209
|
*/
|
|
14202
|
-
var AmpCheck = class
|
|
14210
|
+
var AmpCheck = class extends ToolCheck {
|
|
14203
14211
|
frontmatter;
|
|
14204
14212
|
body;
|
|
14205
14213
|
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
14206
14214
|
if (rest.validate !== false) {
|
|
14207
|
-
const result =
|
|
14215
|
+
const result = new.target.getFrontmatterSchema().safeParse(frontmatter);
|
|
14208
14216
|
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
14209
14217
|
}
|
|
14210
14218
|
super({
|
|
@@ -14214,6 +14222,26 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14214
14222
|
this.frontmatter = frontmatter;
|
|
14215
14223
|
this.body = body;
|
|
14216
14224
|
}
|
|
14225
|
+
/**
|
|
14226
|
+
* The tool target this check format belongs to, which is also the name of
|
|
14227
|
+
* its tool-specific frontmatter section. Goose reads the same per-file
|
|
14228
|
+
* format with a few extra keys, so `GooseCheck` reuses this class through
|
|
14229
|
+
* these hooks.
|
|
14230
|
+
*/
|
|
14231
|
+
static getCheckToolTarget() {
|
|
14232
|
+
return "amp";
|
|
14233
|
+
}
|
|
14234
|
+
/** The frontmatter schema a check file of this tool is validated against. */
|
|
14235
|
+
static getFrontmatterSchema() {
|
|
14236
|
+
return AmpCheckFrontmatterSchema;
|
|
14237
|
+
}
|
|
14238
|
+
/**
|
|
14239
|
+
* Frontmatter keys only this tool reads. Import moves them into the tool's
|
|
14240
|
+
* own section so they are not passed on to every other checks target.
|
|
14241
|
+
*/
|
|
14242
|
+
static getToolOnlyKeys() {
|
|
14243
|
+
return [];
|
|
14244
|
+
}
|
|
14217
14245
|
static getSettablePaths({ global = false } = {}) {
|
|
14218
14246
|
return { relativeDirPath: global ? AMP_CHECKS_GLOBAL_DIR : AMP_CHECKS_PROJECT_DIR };
|
|
14219
14247
|
}
|
|
@@ -14225,6 +14253,10 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14225
14253
|
}
|
|
14226
14254
|
toRulesyncCheck() {
|
|
14227
14255
|
const { name: _name, description, "severity-default": severityDefault, tools, ...restFields } = this.frontmatter;
|
|
14256
|
+
const checkClass = this.constructor;
|
|
14257
|
+
const toolOnlyKeys = new Set(checkClass.getToolOnlyKeys());
|
|
14258
|
+
const sharedFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolOnlyKeys.has(key)));
|
|
14259
|
+
const toolSection = Object.fromEntries(Object.entries(restFields).filter(([key]) => toolOnlyKeys.has(key)));
|
|
14228
14260
|
return new RulesyncCheck({
|
|
14229
14261
|
outputRoot: ".",
|
|
14230
14262
|
frontmatter: {
|
|
@@ -14232,7 +14264,8 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14232
14264
|
...description !== void 0 && { description },
|
|
14233
14265
|
...severityDefault !== void 0 && { severity: severityDefault },
|
|
14234
14266
|
...tools !== void 0 && { tools },
|
|
14235
|
-
...
|
|
14267
|
+
...sharedFields,
|
|
14268
|
+
...Object.keys(toolSection).length > 0 && { [checkClass.getCheckToolTarget()]: toolSection }
|
|
14236
14269
|
},
|
|
14237
14270
|
body: this.body,
|
|
14238
14271
|
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
@@ -14247,22 +14280,24 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14247
14280
|
const { targets: _targets, description, severity, tools, ...restFields } = rulesyncFrontmatter;
|
|
14248
14281
|
const toolTargetKeys = new Set(ALL_TOOL_TARGETS);
|
|
14249
14282
|
const passthroughFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolTargetKeys.has(key) && key !== "name"));
|
|
14250
|
-
const
|
|
14283
|
+
const toolTarget = this.getCheckToolTarget();
|
|
14284
|
+
const rawSection = rulesyncFrontmatter[toolTarget];
|
|
14285
|
+
const toolSection = this.filterToolSpecificSection(rawSection !== null && typeof rawSection === "object" && !Array.isArray(rawSection) ? rawSection : {}, ["name"]);
|
|
14251
14286
|
const rawAmpFrontmatter = {
|
|
14252
14287
|
name,
|
|
14253
14288
|
...description !== void 0 && { description },
|
|
14254
14289
|
...severity !== void 0 && { "severity-default": severity },
|
|
14255
14290
|
...tools !== void 0 && { tools },
|
|
14256
14291
|
...passthroughFields,
|
|
14257
|
-
...
|
|
14292
|
+
...toolSection
|
|
14258
14293
|
};
|
|
14259
|
-
const result =
|
|
14260
|
-
if (!result.success) throw new Error(`Invalid
|
|
14294
|
+
const result = this.getFrontmatterSchema().safeParse(rawAmpFrontmatter);
|
|
14295
|
+
if (!result.success) throw new Error(`Invalid ${toolTarget} check frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
|
|
14261
14296
|
const ampFrontmatter = result.data;
|
|
14262
14297
|
const body = rulesyncCheck.getBody();
|
|
14263
14298
|
const fileContent = stringifyFrontmatter(body, ampFrontmatter);
|
|
14264
14299
|
const paths = this.getSettablePaths({ global });
|
|
14265
|
-
return new
|
|
14300
|
+
return new this({
|
|
14266
14301
|
outputRoot,
|
|
14267
14302
|
frontmatter: ampFrontmatter,
|
|
14268
14303
|
body,
|
|
@@ -14277,7 +14312,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14277
14312
|
success: true,
|
|
14278
14313
|
error: null
|
|
14279
14314
|
};
|
|
14280
|
-
const result =
|
|
14315
|
+
const result = this.constructor.getFrontmatterSchema().safeParse(this.frontmatter);
|
|
14281
14316
|
if (result.success) return {
|
|
14282
14317
|
success: true,
|
|
14283
14318
|
error: null
|
|
@@ -14290,7 +14325,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14290
14325
|
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
14291
14326
|
return this.isTargetedByRulesyncCheckDefault({
|
|
14292
14327
|
rulesyncCheck,
|
|
14293
|
-
toolTarget:
|
|
14328
|
+
toolTarget: this.getCheckToolTarget()
|
|
14294
14329
|
});
|
|
14295
14330
|
}
|
|
14296
14331
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
@@ -14298,9 +14333,9 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14298
14333
|
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
14299
14334
|
const fileContent = await readFileContent(filePath);
|
|
14300
14335
|
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
14301
|
-
const result =
|
|
14336
|
+
const result = this.getFrontmatterSchema().safeParse(frontmatter);
|
|
14302
14337
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
14303
|
-
return new
|
|
14338
|
+
return new this({
|
|
14304
14339
|
outputRoot,
|
|
14305
14340
|
relativeDirPath: paths.relativeDirPath,
|
|
14306
14341
|
relativeFilePath,
|
|
@@ -14311,7 +14346,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14311
14346
|
});
|
|
14312
14347
|
}
|
|
14313
14348
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
14314
|
-
return new
|
|
14349
|
+
return new this({
|
|
14315
14350
|
outputRoot,
|
|
14316
14351
|
relativeDirPath,
|
|
14317
14352
|
relativeFilePath,
|
|
@@ -15411,6 +15446,60 @@ var GitlabduoCheck = class GitlabduoCheck extends ToolCheck {
|
|
|
15411
15446
|
}
|
|
15412
15447
|
};
|
|
15413
15448
|
//#endregion
|
|
15449
|
+
//#region src/constants/goose-paths.ts
|
|
15450
|
+
const GOOSE_DIR = ".goose";
|
|
15451
|
+
const GOOSE_GLOBAL_DIR = join(".config", "goose");
|
|
15452
|
+
const GOOSE_RULE_FILE_NAME = ".goosehints";
|
|
15453
|
+
const GOOSE_MCP_FILE_NAME = "config.yaml";
|
|
15454
|
+
const GOOSE_PERMISSIONS_FILE_NAME = "permission.yaml";
|
|
15455
|
+
const GOOSE_HOOKS_DIR_PATH = join(".agents", "plugins", "rulesync", "hooks");
|
|
15456
|
+
const GOOSE_HOOKS_FILE_NAME = "hooks.json";
|
|
15457
|
+
const GOOSE_PLUGIN_MCP_DIR_PATH = join(".agents", "plugins", "rulesync");
|
|
15458
|
+
const GOOSE_PLUGIN_MCP_FILE_NAME = ".mcp.json";
|
|
15459
|
+
const GOOSE_SKILLS_DIR_PATH = join(GOOSE_DIR, "skills");
|
|
15460
|
+
const GOOSE_GLOBAL_SKILLS_DIR_PATH = join(GOOSE_GLOBAL_DIR, "skills");
|
|
15461
|
+
const GOOSE_RECIPES_DIR_PATH = join(GOOSE_DIR, "recipes");
|
|
15462
|
+
const GOOSE_GLOBAL_RECIPES_DIR_PATH = join(GOOSE_GLOBAL_DIR, "recipes");
|
|
15463
|
+
const GOOSE_AGENTS_DIR_PATH = join(GOOSE_DIR, "agents");
|
|
15464
|
+
const GOOSE_GLOBAL_AGENTS_DIR_PATH = join(GOOSE_GLOBAL_DIR, "agents");
|
|
15465
|
+
const GOOSE_CHECKS_PROJECT_DIR = join(".agents", "checks");
|
|
15466
|
+
const GOOSE_CHECKS_GLOBAL_DIR = join(GOOSE_GLOBAL_DIR, "checks");
|
|
15467
|
+
//#endregion
|
|
15468
|
+
//#region src/features/checks/goose-check.ts
|
|
15469
|
+
const GooseCheckFrontmatterSchema = z.extend(AmpCheckFrontmatterSchema, {
|
|
15470
|
+
model: z.optional(z.string()),
|
|
15471
|
+
"turn-limit": z.optional(z.int().check(z.nonnegative()))
|
|
15472
|
+
});
|
|
15473
|
+
/**
|
|
15474
|
+
* Represents a Goose review check (`goose review`), written to the user-wide
|
|
15475
|
+
* `~/.config/goose/checks/` (global scope only).
|
|
15476
|
+
*
|
|
15477
|
+
* Goose discovers repository checks in `.agents/checks/*.md`, the same
|
|
15478
|
+
* cross-tool directory the `amp` checks target already writes (Goose's
|
|
15479
|
+
* `tools` and `severity-default` fields exist for parity with Amp's), so the
|
|
15480
|
+
* project scope is left to `amp` rather than having two targets own the same
|
|
15481
|
+
* files. Its global discovery reads `~/.config/goose/checks/` first, then
|
|
15482
|
+
* `~/.config/agents/checks/`. The file format is Amp's, so this class reuses
|
|
15483
|
+
* `AmpCheck` and adds Goose's `model` and `turn-limit`, which live in the
|
|
15484
|
+
* `goose:` section of a rulesync check; `name` is derived from the file name.
|
|
15485
|
+
*
|
|
15486
|
+
* @see https://github.com/aaif-goose/goose/blob/main/crates/goose/src/checks/mod.rs
|
|
15487
|
+
*/
|
|
15488
|
+
var GooseCheck = class extends AmpCheck {
|
|
15489
|
+
static getCheckToolTarget() {
|
|
15490
|
+
return "goose";
|
|
15491
|
+
}
|
|
15492
|
+
static getFrontmatterSchema() {
|
|
15493
|
+
return GooseCheckFrontmatterSchema;
|
|
15494
|
+
}
|
|
15495
|
+
static getToolOnlyKeys() {
|
|
15496
|
+
return ["model", "turn-limit"];
|
|
15497
|
+
}
|
|
15498
|
+
static getSettablePaths({ global = false } = {}) {
|
|
15499
|
+
return { relativeDirPath: global ? GOOSE_CHECKS_GLOBAL_DIR : GOOSE_CHECKS_PROJECT_DIR };
|
|
15500
|
+
}
|
|
15501
|
+
};
|
|
15502
|
+
//#endregion
|
|
15414
15503
|
//#region src/constants/hermesagent-paths.ts
|
|
15415
15504
|
/**
|
|
15416
15505
|
* Hermes Agent configuration-layout conventions.
|
|
@@ -16139,6 +16228,14 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
16139
16228
|
committedOutput: true
|
|
16140
16229
|
}
|
|
16141
16230
|
}],
|
|
16231
|
+
["goose", {
|
|
16232
|
+
class: GooseCheck,
|
|
16233
|
+
meta: {
|
|
16234
|
+
supportsGlobal: true,
|
|
16235
|
+
supportsProject: false,
|
|
16236
|
+
filePattern: "*.md"
|
|
16237
|
+
}
|
|
16238
|
+
}],
|
|
16142
16239
|
["hermesagent", {
|
|
16143
16240
|
class: HermesagentCheck,
|
|
16144
16241
|
meta: {
|
|
@@ -16184,7 +16281,9 @@ const defaultGetFactory$6 = (target) => {
|
|
|
16184
16281
|
return factory;
|
|
16185
16282
|
};
|
|
16186
16283
|
const allToolTargetKeys$5 = [...toolCheckFactories.keys()];
|
|
16187
|
-
const checksProcessorToolTargets = allToolTargetKeys$5
|
|
16284
|
+
const checksProcessorToolTargets = allToolTargetKeys$5.filter((target) => {
|
|
16285
|
+
return toolCheckFactories.get(target)?.meta.supportsProject !== false;
|
|
16286
|
+
});
|
|
16188
16287
|
const checksProcessorToolTargetsGlobal = allToolTargetKeys$5.filter((target) => {
|
|
16189
16288
|
return toolCheckFactories.get(target)?.meta.supportsGlobal ?? false;
|
|
16190
16289
|
});
|
|
@@ -16355,23 +16454,6 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
16355
16454
|
}
|
|
16356
16455
|
};
|
|
16357
16456
|
//#endregion
|
|
16358
|
-
//#region src/constants/goose-paths.ts
|
|
16359
|
-
const GOOSE_DIR = ".goose";
|
|
16360
|
-
const GOOSE_GLOBAL_DIR = join(".config", "goose");
|
|
16361
|
-
const GOOSE_RULE_FILE_NAME = ".goosehints";
|
|
16362
|
-
const GOOSE_MCP_FILE_NAME = "config.yaml";
|
|
16363
|
-
const GOOSE_PERMISSIONS_FILE_NAME = "permission.yaml";
|
|
16364
|
-
const GOOSE_HOOKS_DIR_PATH = join(".agents", "plugins", "rulesync", "hooks");
|
|
16365
|
-
const GOOSE_HOOKS_FILE_NAME = "hooks.json";
|
|
16366
|
-
const GOOSE_PLUGIN_MCP_DIR_PATH = join(".agents", "plugins", "rulesync");
|
|
16367
|
-
const GOOSE_PLUGIN_MCP_FILE_NAME = ".mcp.json";
|
|
16368
|
-
const GOOSE_SKILLS_DIR_PATH = join(GOOSE_DIR, "skills");
|
|
16369
|
-
const GOOSE_GLOBAL_SKILLS_DIR_PATH = join(GOOSE_GLOBAL_DIR, "skills");
|
|
16370
|
-
const GOOSE_RECIPES_DIR_PATH = join(GOOSE_DIR, "recipes");
|
|
16371
|
-
const GOOSE_GLOBAL_RECIPES_DIR_PATH = join(GOOSE_GLOBAL_DIR, "recipes");
|
|
16372
|
-
const GOOSE_AGENTS_DIR_PATH = join(GOOSE_DIR, "agents");
|
|
16373
|
-
const GOOSE_GLOBAL_AGENTS_DIR_PATH = join(GOOSE_GLOBAL_DIR, "agents");
|
|
16374
|
-
//#endregion
|
|
16375
16457
|
//#region src/utils/tool-home.ts
|
|
16376
16458
|
/**
|
|
16377
16459
|
* Where the rulesync-side source files of a tool with a home override belong.
|
|
@@ -18093,27 +18175,71 @@ const CLINE_HOOKS_GLOBAL_DIR_PATH = join("Documents", "Cline", "Hooks");
|
|
|
18093
18175
|
const CLINE_HOOKS_MANIFEST_FILE_NAME = "rulesync-hooks.json";
|
|
18094
18176
|
//#endregion
|
|
18095
18177
|
//#region src/features/commands/cline-command.ts
|
|
18178
|
+
const CLINE_WORKFLOW_FIELD_TYPES = {
|
|
18179
|
+
name: "string",
|
|
18180
|
+
disabled: "boolean",
|
|
18181
|
+
enabled: "boolean"
|
|
18182
|
+
};
|
|
18183
|
+
function sanitizeClineWorkflowFrontmatter({ cline, relativeFilePath, logger }) {
|
|
18184
|
+
if (!isRecord$1(cline)) return {};
|
|
18185
|
+
return Object.fromEntries(Object.entries(cline).filter(([key, value]) => {
|
|
18186
|
+
const expected = Object.hasOwn(CLINE_WORKFLOW_FIELD_TYPES, key) ? CLINE_WORKFLOW_FIELD_TYPES[key] : void 0;
|
|
18187
|
+
if (expected === void 0 || typeof value === expected) return true;
|
|
18188
|
+
warnWithFallback(logger, `Cline commands: dropping cline.${key} from ${relativeFilePath}: Cline rejects the whole workflow unless it is a ${expected}.`);
|
|
18189
|
+
return false;
|
|
18190
|
+
}));
|
|
18191
|
+
}
|
|
18192
|
+
/**
|
|
18193
|
+
* Cline workflows (commands). A workflow may carry YAML frontmatter that Cline
|
|
18194
|
+
* reads: `name` (the slash-command name) and a `disabled` / `enabled` switch
|
|
18195
|
+
* (`parseWorkflowConfigFromMarkdown` in Cline's
|
|
18196
|
+
* `user-instruction-config-loader.ts`). The `cline:` section of a
|
|
18197
|
+
* `.rulesync/commands/*.md` file is written as that frontmatter, and every key
|
|
18198
|
+
* of an imported workflow's frontmatter is lifted back into `cline:`, so
|
|
18199
|
+
* hand-written workflow frontmatter survives an import → generate round trip.
|
|
18200
|
+
* Cline's own UI toggle is kept in Cline's state, not in the file. A workflow
|
|
18201
|
+
* without a `cline:` section is written as a bare body, as before.
|
|
18202
|
+
*
|
|
18203
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/extensions/config/user-instruction-config-loader.ts
|
|
18204
|
+
*/
|
|
18096
18205
|
var ClineCommand = class ClineCommand extends ToolCommand {
|
|
18097
18206
|
static getSettablePaths({ global } = {}) {
|
|
18098
18207
|
if (global) return { relativeDirPath: CLINE_COMMANDS_GLOBAL_DIR_PATH };
|
|
18099
18208
|
return { relativeDirPath: CLINE_COMMANDS_DIR_PATH };
|
|
18100
18209
|
}
|
|
18210
|
+
/** The workflow's own frontmatter, or an empty object for a bare body. */
|
|
18211
|
+
getClineFrontmatter() {
|
|
18212
|
+
const { frontmatter } = parseFrontmatter(this.getFileContent(), this.getFilePath());
|
|
18213
|
+
return isRecord$1(frontmatter) ? frontmatter : {};
|
|
18214
|
+
}
|
|
18101
18215
|
toRulesyncCommand() {
|
|
18216
|
+
const cline = this.getClineFrontmatter();
|
|
18217
|
+
const rulesyncFrontmatter = {
|
|
18218
|
+
targets: ["*"],
|
|
18219
|
+
...Object.keys(cline).length > 0 && { cline }
|
|
18220
|
+
};
|
|
18221
|
+
const body = this.getBody();
|
|
18102
18222
|
return new RulesyncCommand({
|
|
18103
18223
|
outputRoot: process.cwd(),
|
|
18104
|
-
frontmatter:
|
|
18105
|
-
body
|
|
18224
|
+
frontmatter: rulesyncFrontmatter,
|
|
18225
|
+
body,
|
|
18106
18226
|
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
18107
18227
|
relativeFilePath: this.relativeFilePath,
|
|
18108
|
-
fileContent:
|
|
18228
|
+
fileContent: stringifyFrontmatter(body, rulesyncFrontmatter),
|
|
18109
18229
|
validate: true
|
|
18110
18230
|
});
|
|
18111
18231
|
}
|
|
18112
|
-
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
18232
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false, logger }) {
|
|
18113
18233
|
const paths = this.getSettablePaths({ global });
|
|
18234
|
+
const cline = sanitizeClineWorkflowFrontmatter({
|
|
18235
|
+
cline: rulesyncCommand.getFrontmatter().cline,
|
|
18236
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
18237
|
+
logger
|
|
18238
|
+
});
|
|
18239
|
+
const body = rulesyncCommand.getBody();
|
|
18114
18240
|
return new ClineCommand({
|
|
18115
18241
|
outputRoot,
|
|
18116
|
-
fileContent:
|
|
18242
|
+
fileContent: Object.keys(cline).length > 0 ? stringifyFrontmatter(body, cline) : body,
|
|
18117
18243
|
relativeDirPath: paths.relativeDirPath,
|
|
18118
18244
|
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
18119
18245
|
validate
|
|
@@ -18126,7 +18252,7 @@ var ClineCommand = class ClineCommand extends ToolCommand {
|
|
|
18126
18252
|
};
|
|
18127
18253
|
}
|
|
18128
18254
|
getBody() {
|
|
18129
|
-
return this.getFileContent();
|
|
18255
|
+
return parseFrontmatter(this.getFileContent(), this.getFilePath()).body.trim();
|
|
18130
18256
|
}
|
|
18131
18257
|
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
18132
18258
|
return this.isTargetedByRulesyncCommandDefault({
|
|
@@ -18137,12 +18263,13 @@ var ClineCommand = class ClineCommand extends ToolCommand {
|
|
|
18137
18263
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
18138
18264
|
const paths = this.getSettablePaths({ global });
|
|
18139
18265
|
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
18140
|
-
const { body
|
|
18266
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
18267
|
+
const cline = isRecord$1(frontmatter) ? frontmatter : {};
|
|
18141
18268
|
return new ClineCommand({
|
|
18142
18269
|
outputRoot,
|
|
18143
18270
|
relativeDirPath: paths.relativeDirPath,
|
|
18144
18271
|
relativeFilePath,
|
|
18145
|
-
fileContent:
|
|
18272
|
+
fileContent: Object.keys(cline).length > 0 ? stringifyFrontmatter(body.trim(), cline) : body.trim(),
|
|
18146
18273
|
validate
|
|
18147
18274
|
});
|
|
18148
18275
|
}
|
|
@@ -32715,6 +32842,31 @@ const PI_PROMPT_BLOCKING_EVENT = "input";
|
|
|
32715
32842
|
* @see https://github.com/earendil-works/pi/blob/v0.87.1/packages/coding-agent/src/core/extensions/types.ts
|
|
32716
32843
|
*/
|
|
32717
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";
|
|
32718
32870
|
const PI_EXTENSION_DIALECT = {
|
|
32719
32871
|
overrideKey: "pi",
|
|
32720
32872
|
typesPackage: "@earendil-works/pi-coding-agent",
|
|
@@ -32736,7 +32888,8 @@ const OMP_EXTENSION_DIALECT = {
|
|
|
32736
32888
|
const PI_BLOCKING_MODE_BY_EVENT = {
|
|
32737
32889
|
[PI_BLOCKING_EVENT]: "tool",
|
|
32738
32890
|
[PI_PROMPT_BLOCKING_EVENT]: "prompt",
|
|
32739
|
-
[PI_SETTLE_BLOCKING_EVENT]: "settle"
|
|
32891
|
+
[PI_SETTLE_BLOCKING_EVENT]: "settle",
|
|
32892
|
+
[OMP_SESSION_STOP_BLOCKING_EVENT]: "sessionStop"
|
|
32740
32893
|
};
|
|
32741
32894
|
/**
|
|
32742
32895
|
* The body emitted inside a blocking handler's `catch`, per gate. `tool_call`
|
|
@@ -32749,7 +32902,8 @@ function failureLinesByMode(dialect) {
|
|
|
32749
32902
|
none: [],
|
|
32750
32903
|
tool: ["return { block: true, reason: toBlockReason(error) };"],
|
|
32751
32904
|
prompt: ["reportPromptGateFailure(ctx, toBlockReason(error));", dialect.promptCancelLine],
|
|
32752
|
-
settle: ["stopHookFailures.push(error);"]
|
|
32905
|
+
settle: ["stopHookFailures.push(error);"],
|
|
32906
|
+
sessionStop: ["stopHookFailures.push(error);"]
|
|
32753
32907
|
};
|
|
32754
32908
|
}
|
|
32755
32909
|
/**
|
|
@@ -32841,6 +32995,12 @@ const PROMPT_GATE_HELPER_LINES = [
|
|
|
32841
32995
|
" console.error(reason);",
|
|
32842
32996
|
"}"
|
|
32843
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
|
+
];
|
|
32844
33004
|
/**
|
|
32845
33005
|
* Helper emitted alongside the stop hook's settle gate. `event.entries` holds
|
|
32846
33006
|
* the drafts earlier extensions proposed at this boundary, so they are kept and
|
|
@@ -32851,10 +33011,6 @@ const PROMPT_GATE_HELPER_LINES = [
|
|
|
32851
33011
|
* appended message is committed, and Pi re-validates the final context itself.
|
|
32852
33012
|
*/
|
|
32853
33013
|
const STOP_HOOK_HELPER_LINES = [
|
|
32854
|
-
"function isStopHookBlock(error: unknown): boolean {",
|
|
32855
|
-
" return (error as { code?: unknown } | null)?.code === 2;",
|
|
32856
|
-
"}",
|
|
32857
|
-
"",
|
|
32858
33014
|
"function continueAfterStopHook(",
|
|
32859
33015
|
" event: AgentBeforeSettleEvent,",
|
|
32860
33016
|
" reason: string,",
|
|
@@ -32937,14 +33093,16 @@ function buildSubscriptionLines(handlerGroups, dialect) {
|
|
|
32937
33093
|
const blocking = PI_BLOCKING_MODE_BY_EVENT[piEvent] ?? "none";
|
|
32938
33094
|
const isPromptGate = blocking === "prompt";
|
|
32939
33095
|
const isSettleGate = blocking === "settle";
|
|
33096
|
+
const isSessionStopGate = blocking === "sessionStop";
|
|
33097
|
+
const collectsStopFailures = isSettleGate || isSessionStopGate;
|
|
32940
33098
|
const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
|
|
32941
33099
|
const usesErrorFlag = handlers.some((h) => h.onlyOnError);
|
|
32942
33100
|
const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
|
|
32943
|
-
const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate ||
|
|
33101
|
+
const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate || collectsStopFailures ? "event" : "";
|
|
32944
33102
|
lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${params}) => {`);
|
|
32945
33103
|
if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
|
|
32946
33104
|
if (isPromptGate) lines.push(` if (event.source === "extension") ${dialect.promptPassLine}`);
|
|
32947
|
-
if (
|
|
33105
|
+
if (collectsStopFailures) lines.push(" const stopHookFailures: unknown[] = [];");
|
|
32948
33106
|
for (const handler of handlers) lines.push(...buildCommandLines({
|
|
32949
33107
|
handler,
|
|
32950
33108
|
usesToolName,
|
|
@@ -32960,6 +33118,17 @@ function buildSubscriptionLines(handlerGroups, dialect) {
|
|
|
32960
33118
|
lines.push(" }");
|
|
32961
33119
|
lines.push(" if (stopHookFailures.length > 0) throw stopHookFailures[0];");
|
|
32962
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
|
+
}
|
|
32963
33132
|
lines.push(" });");
|
|
32964
33133
|
if (isSettleGate) {
|
|
32965
33134
|
lines.push(` pi.on("agent_settled", async () => {`);
|
|
@@ -32978,8 +33147,9 @@ function buildSubscriptionLines(handlerGroups, dialect) {
|
|
|
32978
33147
|
* `{ block: true, reason }`, and on `input` — Pi's prompt-submission gate —
|
|
32979
33148
|
* where a non-zero exit cancels the prompt with the dialect's cancel result
|
|
32980
33149
|
* (`{ action: "handled" }` for Pi, `{ handled: true }` for oh-my-pi), and
|
|
32981
|
-
* on `agent_before_settle
|
|
32982
|
-
* 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.
|
|
32983
33153
|
* `postToolUse` and `postToolUseFailure` share Pi's `tool_result` event; the
|
|
32984
33154
|
* latter's commands run only when `event.isError` is set.
|
|
32985
33155
|
*
|
|
@@ -33000,7 +33170,8 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
|
|
|
33000
33170
|
const subscriptionLines = buildSubscriptionLines(handlerGroups, dialect);
|
|
33001
33171
|
const hasPromptGate = Boolean(handlerGroups[PI_PROMPT_BLOCKING_EVENT]);
|
|
33002
33172
|
const hasSettleGate = Boolean(handlerGroups[PI_SETTLE_BLOCKING_EVENT]);
|
|
33003
|
-
const
|
|
33173
|
+
const hasSessionStopGate = Boolean(handlerGroups[OMP_SESSION_STOP_BLOCKING_EVENT]);
|
|
33174
|
+
const needsBlockReasonHelper = Boolean(handlerGroups[PI_BLOCKING_EVENT]) || hasPromptGate || hasSettleGate || hasSessionStopGate;
|
|
33004
33175
|
const lines = ["// Generated by rulesync. Do not edit manually."];
|
|
33005
33176
|
if (subscriptionLines.length === 0) {
|
|
33006
33177
|
lines.push("export default function () {}");
|
|
@@ -33027,6 +33198,10 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
|
|
|
33027
33198
|
lines.push(...PROMPT_GATE_HELPER_LINES);
|
|
33028
33199
|
lines.push("");
|
|
33029
33200
|
}
|
|
33201
|
+
if (hasSettleGate || hasSessionStopGate) {
|
|
33202
|
+
lines.push(...STOP_HOOK_BLOCK_HELPER_LINES);
|
|
33203
|
+
lines.push("");
|
|
33204
|
+
}
|
|
33030
33205
|
if (hasSettleGate) {
|
|
33031
33206
|
lines.push(...STOP_HOOK_HELPER_LINES);
|
|
33032
33207
|
lines.push("");
|
|
@@ -40522,6 +40697,35 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
40522
40697
|
}
|
|
40523
40698
|
return result;
|
|
40524
40699
|
}
|
|
40700
|
+
/**
|
|
40701
|
+
* Keys Codex refuses on each transport. Its `McpServerConfig` deserializer
|
|
40702
|
+
* calls `throw_if_set` for these, and the error fails the whole `config.toml`,
|
|
40703
|
+
* not just the one server, so they are dropped with a warning instead of
|
|
40704
|
+
* written. Checked against `codex-rs/config/src/mcp_types.rs` at rust-v0.162.1.
|
|
40705
|
+
*/
|
|
40706
|
+
const CODEX_STDIO_REJECTED_KEYS = [
|
|
40707
|
+
"url",
|
|
40708
|
+
"bearer_token_env_var",
|
|
40709
|
+
"bearer_token",
|
|
40710
|
+
"http_headers_helper",
|
|
40711
|
+
"http_headers",
|
|
40712
|
+
"env_http_headers",
|
|
40713
|
+
"oauth",
|
|
40714
|
+
"oauth_resource",
|
|
40715
|
+
"auth"
|
|
40716
|
+
];
|
|
40717
|
+
const CODEX_HTTP_REJECTED_KEYS = [
|
|
40718
|
+
"args",
|
|
40719
|
+
"env",
|
|
40720
|
+
"env_vars",
|
|
40721
|
+
"cwd",
|
|
40722
|
+
"bearer_token"
|
|
40723
|
+
];
|
|
40724
|
+
function dropTransportRejectedKeys({ converted, isStdio, serverName }) {
|
|
40725
|
+
const dropped = (isStdio ? CODEX_STDIO_REJECTED_KEYS : CODEX_HTTP_REJECTED_KEYS).filter((key) => converted[key] !== void 0);
|
|
40726
|
+
for (const key of dropped) delete converted[key];
|
|
40727
|
+
if (dropped.length > 0) warnWithFallback(void 0, `[CodexCliMcp] Dropping ${dropped.map((key) => `'${key}'`).join(", ")} from ${isStdio ? "stdio" : "url-based"} MCP server "${serverName}": Codex rejects ${dropped.length === 1 ? "this key" : "these keys"} on a ${isStdio ? "stdio" : "url-based"} server and would fail to load the whole config.toml.`);
|
|
40728
|
+
}
|
|
40525
40729
|
function convertToCodexFormat(mcpServers) {
|
|
40526
40730
|
const result = {};
|
|
40527
40731
|
const originalNames = /* @__PURE__ */ new Map();
|
|
@@ -40564,6 +40768,11 @@ function convertToCodexFormat(mcpServers) {
|
|
|
40564
40768
|
converted,
|
|
40565
40769
|
serverName: name
|
|
40566
40770
|
});
|
|
40771
|
+
dropTransportRejectedKeys({
|
|
40772
|
+
converted,
|
|
40773
|
+
isStdio,
|
|
40774
|
+
serverName: name
|
|
40775
|
+
});
|
|
40567
40776
|
const previousName = originalNames.get(codexName);
|
|
40568
40777
|
if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
|
|
40569
40778
|
originalNames.set(codexName, name);
|
|
@@ -55789,6 +55998,124 @@ function parseCodebuddySettings({ fileContent, relativePath }) {
|
|
|
55789
55998
|
}
|
|
55790
55999
|
}
|
|
55791
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
|
|
55792
56119
|
//#region src/features/permissions/codewhale-repo-law.ts
|
|
55793
56120
|
/**
|
|
55794
56121
|
* Project-scope Codewhale permissions: the enforced `protected_invariants` of
|
|
@@ -55939,7 +56266,8 @@ function collectRestrictions({ config, logger }) {
|
|
|
55939
56266
|
let skippedAllowCount = 0;
|
|
55940
56267
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
55941
56268
|
if (!REPO_LAW_CATEGORIES.includes(category)) {
|
|
55942
|
-
|
|
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);
|
|
55943
56271
|
continue;
|
|
55944
56272
|
}
|
|
55945
56273
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -56665,17 +56993,32 @@ async function warnOnShadowedConstitution({ outputRoot, logger }) {
|
|
|
56665
56993
|
* Project scope manages the enforced `protected_invariants` of the repo
|
|
56666
56994
|
* constitution `.codewhale/constitution.json` (see `codewhale-repo-law.ts`):
|
|
56667
56995
|
* the project `.codewhale/config.toml` overlay cannot carry rules, but repo
|
|
56668
|
-
* 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.
|
|
56669
56999
|
*
|
|
56670
57000
|
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/CONFIGURATION.md
|
|
56671
57001
|
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/AUTHORIZATION_ORDER.md
|
|
56672
57002
|
*/
|
|
56673
57003
|
var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
|
|
56674
|
-
|
|
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 }) {
|
|
56675
57010
|
super({
|
|
56676
57011
|
...params,
|
|
56677
57012
|
fileContent: params.fileContent ?? ""
|
|
56678
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 }];
|
|
56679
57022
|
}
|
|
56680
57023
|
/**
|
|
56681
57024
|
* Codewhale's approval card writes to the global file, and the constitution
|
|
@@ -56712,7 +57055,11 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
|
|
|
56712
57055
|
relativeFilePath: paths.relativeFilePath,
|
|
56713
57056
|
fileContent,
|
|
56714
57057
|
validate,
|
|
56715
|
-
global
|
|
57058
|
+
global,
|
|
57059
|
+
projectShellDisabled: global ? false : await readCodewhaleProjectShellDisabled({
|
|
57060
|
+
outputRoot,
|
|
57061
|
+
logger
|
|
57062
|
+
})
|
|
56716
57063
|
});
|
|
56717
57064
|
}
|
|
56718
57065
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
@@ -56774,11 +57121,15 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
|
|
|
56774
57121
|
toRulesyncPermissions() {
|
|
56775
57122
|
const filePath = join(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
56776
57123
|
if (!this.global) {
|
|
56777
|
-
const
|
|
57124
|
+
const permission = codewhaleConstitutionToCanonical(parseCodewhaleConstitution({
|
|
56778
57125
|
fileContent: this.getFileContent(),
|
|
56779
57126
|
filePath
|
|
56780
|
-
});
|
|
56781
|
-
|
|
57127
|
+
}));
|
|
57128
|
+
if (this.projectShellDisabled) permission.bash = {
|
|
57129
|
+
...permission.bash,
|
|
57130
|
+
"*": "deny"
|
|
57131
|
+
};
|
|
57132
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
56782
57133
|
}
|
|
56783
57134
|
const { permission, nativeRules } = codewhaleRulesToCanonical(partitionRuleRecords(parsePermissionsDocument({
|
|
56784
57135
|
fileContent: this.getFileContent(),
|
|
@@ -67784,10 +68135,15 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
67784
68135
|
outputRoot,
|
|
67785
68136
|
logger
|
|
67786
68137
|
});
|
|
68138
|
+
const inheritedProvider = global ? void 0 : await readGlobalTaktProvider({
|
|
68139
|
+
projectFilePath: filePath,
|
|
68140
|
+
logger
|
|
68141
|
+
});
|
|
67787
68142
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
67788
68143
|
const provider = resolveActiveProvider({
|
|
67789
68144
|
config,
|
|
67790
|
-
runtime: runtime.active ? runtime.config : void 0
|
|
68145
|
+
runtime: runtime.active ? runtime.config : void 0,
|
|
68146
|
+
inheritedProvider
|
|
67791
68147
|
});
|
|
67792
68148
|
const mode = deriveTaktPermissionMode(rulesyncJson);
|
|
67793
68149
|
const override = isPlainObject$1(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
|
|
@@ -67897,10 +68253,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
67897
68253
|
* `provider_profiles` (unless that key is the pre-0.68.0 `claude` fallback),
|
|
67898
68254
|
* else the `claude-sdk` default (Takt 0.68.0+).
|
|
67899
68255
|
*/
|
|
67900
|
-
function resolveActiveProvider({ config, runtime }) {
|
|
68256
|
+
function resolveActiveProvider({ config, runtime, inheritedProvider }) {
|
|
67901
68257
|
const fromRuntime = runtime === void 0 ? void 0 : resolveRuntimeProvider(runtime);
|
|
67902
68258
|
if (fromRuntime !== void 0) return fromRuntime;
|
|
67903
|
-
|
|
68259
|
+
const local = readProviderName(config);
|
|
68260
|
+
if (local !== void 0) return local;
|
|
68261
|
+
if (inheritedProvider !== void 0) return inheritedProvider;
|
|
67904
68262
|
const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
|
|
67905
68263
|
if (isPlainObject$1(profiles)) {
|
|
67906
68264
|
const keys = Object.keys(profiles);
|
|
@@ -67908,6 +68266,43 @@ function resolveActiveProvider({ config, runtime }) {
|
|
|
67908
68266
|
}
|
|
67909
68267
|
return TAKT_DEFAULT_PROVIDER;
|
|
67910
68268
|
}
|
|
68269
|
+
/**
|
|
68270
|
+
* The top-level `provider:` of a Takt `config.yaml`, when it names one: either
|
|
68271
|
+
* the plain name or the block form `provider: { type: <name>, ... }` Takt also
|
|
68272
|
+
* accepts (`ConfigProviderReference`).
|
|
68273
|
+
*/
|
|
68274
|
+
function readProviderName(config) {
|
|
68275
|
+
const raw = config[TAKT_PROVIDER_KEY];
|
|
68276
|
+
const provider = isPlainObject$1(raw) ? raw.type : raw;
|
|
68277
|
+
return typeof provider === "string" && provider.trim() !== "" ? provider : void 0;
|
|
68278
|
+
}
|
|
68279
|
+
/**
|
|
68280
|
+
* The top-level `provider:` of the global `~/.takt/config.yaml`, read for a
|
|
68281
|
+
* project-scope generate. An unresolvable home directory, a missing or
|
|
68282
|
+
* unparsable file, or one that names no provider all mean "nothing inherited".
|
|
68283
|
+
*/
|
|
68284
|
+
async function readGlobalTaktProvider({ projectFilePath, logger }) {
|
|
68285
|
+
let globalPath;
|
|
68286
|
+
try {
|
|
68287
|
+
globalPath = join(getHomeDirectory(), TAKT_DIR, TAKT_CONFIG_FILE_NAME);
|
|
68288
|
+
} catch {
|
|
68289
|
+
return;
|
|
68290
|
+
}
|
|
68291
|
+
if (globalPath === projectFilePath) return;
|
|
68292
|
+
try {
|
|
68293
|
+
const content = await readFileContentOrNull(globalPath);
|
|
68294
|
+
if (content === null) return;
|
|
68295
|
+
return readProviderName(parseSharedConfig({
|
|
68296
|
+
format: "yaml",
|
|
68297
|
+
fileContent: content,
|
|
68298
|
+
filePath: globalPath,
|
|
68299
|
+
invalidRootPolicy: "error"
|
|
68300
|
+
}));
|
|
68301
|
+
} catch (error) {
|
|
68302
|
+
logger?.warn(`Takt permissions: could not read the provider from ${globalPath} (${formatError(error)}); falling back to the project config alone.`);
|
|
68303
|
+
return;
|
|
68304
|
+
}
|
|
68305
|
+
}
|
|
67911
68306
|
/** A plain object carrying at least one entry (Takt's "assignment" test). */
|
|
67912
68307
|
function hasEntries(value) {
|
|
67913
68308
|
return isPlainObject$1(value) && Object.keys(value).length > 0;
|
|
@@ -71663,6 +72058,14 @@ var PermissionsProcessor = class extends FeatureProcessor {
|
|
|
71663
72058
|
logger: this.logger,
|
|
71664
72059
|
global: this.global
|
|
71665
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
|
+
}
|
|
71666
72069
|
if (this.toolTarget !== "codexcli") return [toolPermissions];
|
|
71667
72070
|
return [toolPermissions, createCodexcliBashRulesFile({
|
|
71668
72071
|
outputRoot: this.outputRoot,
|
|
@@ -101329,4 +101732,4 @@ async function importChecksCore(params) {
|
|
|
101329
101732
|
//#endregion
|
|
101330
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 };
|
|
101331
101734
|
|
|
101332
|
-
//# sourceMappingURL=import-
|
|
101735
|
+
//# sourceMappingURL=import-DgKWnDni.js.map
|