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
|
@@ -453,36 +453,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
453
453
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
454
454
|
//#endregion
|
|
455
455
|
//#region src/constants/rulesync-paths.ts
|
|
456
|
-
const { join: join$
|
|
456
|
+
const { join: join$417 } = node_path.posix;
|
|
457
457
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
458
458
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
459
459
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
460
460
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
461
461
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
462
462
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
463
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
463
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$417(RULES_FEATURE_SUBDIR, ".curated");
|
|
464
464
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
465
465
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
466
466
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
467
467
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
468
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
469
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
470
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
471
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
472
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
473
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
474
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
475
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
476
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
477
|
-
join$
|
|
478
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
479
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
468
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$417(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
469
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
470
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
471
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
472
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
473
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
475
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
476
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
477
|
+
join$417(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
478
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
479
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
480
480
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
481
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
481
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
482
482
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
483
483
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
484
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
485
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
484
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
485
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
486
486
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
487
487
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
488
488
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -935,6 +935,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
935
935
|
"cursor",
|
|
936
936
|
"factorydroid",
|
|
937
937
|
"gitlabduo",
|
|
938
|
+
"goose",
|
|
938
939
|
"hermesagent",
|
|
939
940
|
"kilo",
|
|
940
941
|
"qwencode",
|
|
@@ -4844,13 +4845,11 @@ const PI_HOOK_EVENTS = [
|
|
|
4844
4845
|
/**
|
|
4845
4846
|
* Hook events supported by oh-my-pi (`omp`), Pi's fork, through the same
|
|
4846
4847
|
* generated TypeScript extension. oh-my-pi keeps Pi's names for every event
|
|
4847
|
-
* listed here
|
|
4848
|
-
*
|
|
4849
|
-
* `
|
|
4850
|
-
* result; it needs its own handler in the generator and is left for a
|
|
4851
|
-
* follow-up. See CANONICAL_TO_OMP_EVENT_NAMES.
|
|
4848
|
+
* listed here except `stop`: it has no `agent_before_settle`, so `stop` maps to
|
|
4849
|
+
* its own `session_stop` event instead. It has no `ui_prompt_start` either, so
|
|
4850
|
+
* `notification` is not mapped. See CANONICAL_TO_OMP_EVENT_NAMES.
|
|
4852
4851
|
*
|
|
4853
|
-
* @see https://github.com/can1357/oh-my-pi/blob/
|
|
4852
|
+
* @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/extensions.md
|
|
4854
4853
|
*/
|
|
4855
4854
|
const OMP_HOOK_EVENTS = [
|
|
4856
4855
|
"sessionStart",
|
|
@@ -4861,6 +4860,7 @@ const OMP_HOOK_EVENTS = [
|
|
|
4861
4860
|
"preModelInvocation",
|
|
4862
4861
|
"postModelInvocation",
|
|
4863
4862
|
"beforeSubmitPrompt",
|
|
4863
|
+
"stop",
|
|
4864
4864
|
"preCompact",
|
|
4865
4865
|
"postCompact"
|
|
4866
4866
|
];
|
|
@@ -6282,9 +6282,10 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
|
|
|
6282
6282
|
/**
|
|
6283
6283
|
* Map canonical camelCase event names to oh-my-pi extension event names. They
|
|
6284
6284
|
* are Pi's names (see CANONICAL_TO_PI_EVENT_NAMES) for the events oh-my-pi
|
|
6285
|
-
* kept
|
|
6286
|
-
* budget, and fails a `tool_call`
|
|
6287
|
-
* `extensionHandlers.toolCallTimeoutMs`
|
|
6285
|
+
* kept, plus `session_stop` for `stop`. Note that oh-my-pi runs
|
|
6286
|
+
* `session_shutdown` handlers with a 2-second budget, and fails a `tool_call`
|
|
6287
|
+
* closed when its handler exceeds `extensionHandlers.toolCallTimeoutMs`
|
|
6288
|
+
* (30 seconds by default).
|
|
6288
6289
|
*
|
|
6289
6290
|
* @see https://github.com/can1357/oh-my-pi/blob/40e9368ef0458fd9073329cdff4174895f91bc6b/docs/extensions.md
|
|
6290
6291
|
*/
|
|
@@ -6297,6 +6298,7 @@ const CANONICAL_TO_OMP_EVENT_NAMES = {
|
|
|
6297
6298
|
preModelInvocation: "context",
|
|
6298
6299
|
postModelInvocation: "message_end",
|
|
6299
6300
|
beforeSubmitPrompt: "input",
|
|
6301
|
+
stop: "session_stop",
|
|
6300
6302
|
preCompact: "session_before_compact",
|
|
6301
6303
|
postCompact: "session_compact"
|
|
6302
6304
|
};
|
|
@@ -12954,10 +12956,16 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12954
12956
|
[CODEWHALE_CONFIG_SHARED_FILE_KEY]: {
|
|
12955
12957
|
format: "toml",
|
|
12956
12958
|
invalidRootPolicy: "error",
|
|
12957
|
-
features: {
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12959
|
+
features: {
|
|
12960
|
+
hooks: {
|
|
12961
|
+
kind: "replace-owned-keys",
|
|
12962
|
+
ownedKeys: ["hooks"]
|
|
12963
|
+
},
|
|
12964
|
+
permissions: {
|
|
12965
|
+
kind: "replace-owned-keys",
|
|
12966
|
+
ownedKeys: ["allow_shell"]
|
|
12967
|
+
}
|
|
12968
|
+
}
|
|
12961
12969
|
},
|
|
12962
12970
|
".letta/settings.json": {
|
|
12963
12971
|
format: "json",
|
|
@@ -14221,12 +14229,12 @@ const AmpCheckFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
14221
14229
|
* this format continue to apply.
|
|
14222
14230
|
* @see https://ampcode.com/docs/review
|
|
14223
14231
|
*/
|
|
14224
|
-
var AmpCheck = class
|
|
14232
|
+
var AmpCheck = class extends ToolCheck {
|
|
14225
14233
|
frontmatter;
|
|
14226
14234
|
body;
|
|
14227
14235
|
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
14228
14236
|
if (rest.validate !== false) {
|
|
14229
|
-
const result =
|
|
14237
|
+
const result = new.target.getFrontmatterSchema().safeParse(frontmatter);
|
|
14230
14238
|
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
14231
14239
|
}
|
|
14232
14240
|
super({
|
|
@@ -14236,6 +14244,26 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14236
14244
|
this.frontmatter = frontmatter;
|
|
14237
14245
|
this.body = body;
|
|
14238
14246
|
}
|
|
14247
|
+
/**
|
|
14248
|
+
* The tool target this check format belongs to, which is also the name of
|
|
14249
|
+
* its tool-specific frontmatter section. Goose reads the same per-file
|
|
14250
|
+
* format with a few extra keys, so `GooseCheck` reuses this class through
|
|
14251
|
+
* these hooks.
|
|
14252
|
+
*/
|
|
14253
|
+
static getCheckToolTarget() {
|
|
14254
|
+
return "amp";
|
|
14255
|
+
}
|
|
14256
|
+
/** The frontmatter schema a check file of this tool is validated against. */
|
|
14257
|
+
static getFrontmatterSchema() {
|
|
14258
|
+
return AmpCheckFrontmatterSchema;
|
|
14259
|
+
}
|
|
14260
|
+
/**
|
|
14261
|
+
* Frontmatter keys only this tool reads. Import moves them into the tool's
|
|
14262
|
+
* own section so they are not passed on to every other checks target.
|
|
14263
|
+
*/
|
|
14264
|
+
static getToolOnlyKeys() {
|
|
14265
|
+
return [];
|
|
14266
|
+
}
|
|
14239
14267
|
static getSettablePaths({ global = false } = {}) {
|
|
14240
14268
|
return { relativeDirPath: global ? AMP_CHECKS_GLOBAL_DIR : AMP_CHECKS_PROJECT_DIR };
|
|
14241
14269
|
}
|
|
@@ -14247,6 +14275,10 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14247
14275
|
}
|
|
14248
14276
|
toRulesyncCheck() {
|
|
14249
14277
|
const { name: _name, description, "severity-default": severityDefault, tools, ...restFields } = this.frontmatter;
|
|
14278
|
+
const checkClass = this.constructor;
|
|
14279
|
+
const toolOnlyKeys = new Set(checkClass.getToolOnlyKeys());
|
|
14280
|
+
const sharedFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolOnlyKeys.has(key)));
|
|
14281
|
+
const toolSection = Object.fromEntries(Object.entries(restFields).filter(([key]) => toolOnlyKeys.has(key)));
|
|
14250
14282
|
return new RulesyncCheck({
|
|
14251
14283
|
outputRoot: ".",
|
|
14252
14284
|
frontmatter: {
|
|
@@ -14254,7 +14286,8 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14254
14286
|
...description !== void 0 && { description },
|
|
14255
14287
|
...severityDefault !== void 0 && { severity: severityDefault },
|
|
14256
14288
|
...tools !== void 0 && { tools },
|
|
14257
|
-
...
|
|
14289
|
+
...sharedFields,
|
|
14290
|
+
...Object.keys(toolSection).length > 0 && { [checkClass.getCheckToolTarget()]: toolSection }
|
|
14258
14291
|
},
|
|
14259
14292
|
body: this.body,
|
|
14260
14293
|
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
@@ -14269,22 +14302,24 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14269
14302
|
const { targets: _targets, description, severity, tools, ...restFields } = rulesyncFrontmatter;
|
|
14270
14303
|
const toolTargetKeys = new Set(ALL_TOOL_TARGETS);
|
|
14271
14304
|
const passthroughFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolTargetKeys.has(key) && key !== "name"));
|
|
14272
|
-
const
|
|
14305
|
+
const toolTarget = this.getCheckToolTarget();
|
|
14306
|
+
const rawSection = rulesyncFrontmatter[toolTarget];
|
|
14307
|
+
const toolSection = this.filterToolSpecificSection(rawSection !== null && typeof rawSection === "object" && !Array.isArray(rawSection) ? rawSection : {}, ["name"]);
|
|
14273
14308
|
const rawAmpFrontmatter = {
|
|
14274
14309
|
name,
|
|
14275
14310
|
...description !== void 0 && { description },
|
|
14276
14311
|
...severity !== void 0 && { "severity-default": severity },
|
|
14277
14312
|
...tools !== void 0 && { tools },
|
|
14278
14313
|
...passthroughFields,
|
|
14279
|
-
...
|
|
14314
|
+
...toolSection
|
|
14280
14315
|
};
|
|
14281
|
-
const result =
|
|
14282
|
-
if (!result.success) throw new Error(`Invalid
|
|
14316
|
+
const result = this.getFrontmatterSchema().safeParse(rawAmpFrontmatter);
|
|
14317
|
+
if (!result.success) throw new Error(`Invalid ${toolTarget} check frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
|
|
14283
14318
|
const ampFrontmatter = result.data;
|
|
14284
14319
|
const body = rulesyncCheck.getBody();
|
|
14285
14320
|
const fileContent = stringifyFrontmatter(body, ampFrontmatter);
|
|
14286
14321
|
const paths = this.getSettablePaths({ global });
|
|
14287
|
-
return new
|
|
14322
|
+
return new this({
|
|
14288
14323
|
outputRoot,
|
|
14289
14324
|
frontmatter: ampFrontmatter,
|
|
14290
14325
|
body,
|
|
@@ -14299,7 +14334,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14299
14334
|
success: true,
|
|
14300
14335
|
error: null
|
|
14301
14336
|
};
|
|
14302
|
-
const result =
|
|
14337
|
+
const result = this.constructor.getFrontmatterSchema().safeParse(this.frontmatter);
|
|
14303
14338
|
if (result.success) return {
|
|
14304
14339
|
success: true,
|
|
14305
14340
|
error: null
|
|
@@ -14312,7 +14347,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14312
14347
|
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
14313
14348
|
return this.isTargetedByRulesyncCheckDefault({
|
|
14314
14349
|
rulesyncCheck,
|
|
14315
|
-
toolTarget:
|
|
14350
|
+
toolTarget: this.getCheckToolTarget()
|
|
14316
14351
|
});
|
|
14317
14352
|
}
|
|
14318
14353
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
@@ -14320,9 +14355,9 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14320
14355
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
14321
14356
|
const fileContent = await readFileContent(filePath);
|
|
14322
14357
|
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
14323
|
-
const result =
|
|
14358
|
+
const result = this.getFrontmatterSchema().safeParse(frontmatter);
|
|
14324
14359
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
14325
|
-
return new
|
|
14360
|
+
return new this({
|
|
14326
14361
|
outputRoot,
|
|
14327
14362
|
relativeDirPath: paths.relativeDirPath,
|
|
14328
14363
|
relativeFilePath,
|
|
@@ -14333,7 +14368,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14333
14368
|
});
|
|
14334
14369
|
}
|
|
14335
14370
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
14336
|
-
return new
|
|
14371
|
+
return new this({
|
|
14337
14372
|
outputRoot,
|
|
14338
14373
|
relativeDirPath,
|
|
14339
14374
|
relativeFilePath,
|
|
@@ -15433,6 +15468,60 @@ var GitlabduoCheck = class GitlabduoCheck extends ToolCheck {
|
|
|
15433
15468
|
}
|
|
15434
15469
|
};
|
|
15435
15470
|
//#endregion
|
|
15471
|
+
//#region src/constants/goose-paths.ts
|
|
15472
|
+
const GOOSE_DIR = ".goose";
|
|
15473
|
+
const GOOSE_GLOBAL_DIR = (0, node_path.join)(".config", "goose");
|
|
15474
|
+
const GOOSE_RULE_FILE_NAME = ".goosehints";
|
|
15475
|
+
const GOOSE_MCP_FILE_NAME = "config.yaml";
|
|
15476
|
+
const GOOSE_PERMISSIONS_FILE_NAME = "permission.yaml";
|
|
15477
|
+
const GOOSE_HOOKS_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync", "hooks");
|
|
15478
|
+
const GOOSE_HOOKS_FILE_NAME = "hooks.json";
|
|
15479
|
+
const GOOSE_PLUGIN_MCP_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync");
|
|
15480
|
+
const GOOSE_PLUGIN_MCP_FILE_NAME = ".mcp.json";
|
|
15481
|
+
const GOOSE_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "skills");
|
|
15482
|
+
const GOOSE_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "skills");
|
|
15483
|
+
const GOOSE_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "recipes");
|
|
15484
|
+
const GOOSE_GLOBAL_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "recipes");
|
|
15485
|
+
const GOOSE_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "agents");
|
|
15486
|
+
const GOOSE_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "agents");
|
|
15487
|
+
const GOOSE_CHECKS_PROJECT_DIR = (0, node_path.join)(".agents", "checks");
|
|
15488
|
+
const GOOSE_CHECKS_GLOBAL_DIR = (0, node_path.join)(GOOSE_GLOBAL_DIR, "checks");
|
|
15489
|
+
//#endregion
|
|
15490
|
+
//#region src/features/checks/goose-check.ts
|
|
15491
|
+
const GooseCheckFrontmatterSchema = zod_mini.z.extend(AmpCheckFrontmatterSchema, {
|
|
15492
|
+
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
15493
|
+
"turn-limit": zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative()))
|
|
15494
|
+
});
|
|
15495
|
+
/**
|
|
15496
|
+
* Represents a Goose review check (`goose review`), written to the user-wide
|
|
15497
|
+
* `~/.config/goose/checks/` (global scope only).
|
|
15498
|
+
*
|
|
15499
|
+
* Goose discovers repository checks in `.agents/checks/*.md`, the same
|
|
15500
|
+
* cross-tool directory the `amp` checks target already writes (Goose's
|
|
15501
|
+
* `tools` and `severity-default` fields exist for parity with Amp's), so the
|
|
15502
|
+
* project scope is left to `amp` rather than having two targets own the same
|
|
15503
|
+
* files. Its global discovery reads `~/.config/goose/checks/` first, then
|
|
15504
|
+
* `~/.config/agents/checks/`. The file format is Amp's, so this class reuses
|
|
15505
|
+
* `AmpCheck` and adds Goose's `model` and `turn-limit`, which live in the
|
|
15506
|
+
* `goose:` section of a rulesync check; `name` is derived from the file name.
|
|
15507
|
+
*
|
|
15508
|
+
* @see https://github.com/aaif-goose/goose/blob/main/crates/goose/src/checks/mod.rs
|
|
15509
|
+
*/
|
|
15510
|
+
var GooseCheck = class extends AmpCheck {
|
|
15511
|
+
static getCheckToolTarget() {
|
|
15512
|
+
return "goose";
|
|
15513
|
+
}
|
|
15514
|
+
static getFrontmatterSchema() {
|
|
15515
|
+
return GooseCheckFrontmatterSchema;
|
|
15516
|
+
}
|
|
15517
|
+
static getToolOnlyKeys() {
|
|
15518
|
+
return ["model", "turn-limit"];
|
|
15519
|
+
}
|
|
15520
|
+
static getSettablePaths({ global = false } = {}) {
|
|
15521
|
+
return { relativeDirPath: global ? GOOSE_CHECKS_GLOBAL_DIR : GOOSE_CHECKS_PROJECT_DIR };
|
|
15522
|
+
}
|
|
15523
|
+
};
|
|
15524
|
+
//#endregion
|
|
15436
15525
|
//#region src/constants/hermesagent-paths.ts
|
|
15437
15526
|
/**
|
|
15438
15527
|
* Hermes Agent configuration-layout conventions.
|
|
@@ -16161,6 +16250,14 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
16161
16250
|
committedOutput: true
|
|
16162
16251
|
}
|
|
16163
16252
|
}],
|
|
16253
|
+
["goose", {
|
|
16254
|
+
class: GooseCheck,
|
|
16255
|
+
meta: {
|
|
16256
|
+
supportsGlobal: true,
|
|
16257
|
+
supportsProject: false,
|
|
16258
|
+
filePattern: "*.md"
|
|
16259
|
+
}
|
|
16260
|
+
}],
|
|
16164
16261
|
["hermesagent", {
|
|
16165
16262
|
class: HermesagentCheck,
|
|
16166
16263
|
meta: {
|
|
@@ -16206,7 +16303,9 @@ const defaultGetFactory$6 = (target) => {
|
|
|
16206
16303
|
return factory;
|
|
16207
16304
|
};
|
|
16208
16305
|
const allToolTargetKeys$5 = [...toolCheckFactories.keys()];
|
|
16209
|
-
const checksProcessorToolTargets = allToolTargetKeys$5
|
|
16306
|
+
const checksProcessorToolTargets = allToolTargetKeys$5.filter((target) => {
|
|
16307
|
+
return toolCheckFactories.get(target)?.meta.supportsProject !== false;
|
|
16308
|
+
});
|
|
16210
16309
|
const checksProcessorToolTargetsGlobal = allToolTargetKeys$5.filter((target) => {
|
|
16211
16310
|
return toolCheckFactories.get(target)?.meta.supportsGlobal ?? false;
|
|
16212
16311
|
});
|
|
@@ -16377,23 +16476,6 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
16377
16476
|
}
|
|
16378
16477
|
};
|
|
16379
16478
|
//#endregion
|
|
16380
|
-
//#region src/constants/goose-paths.ts
|
|
16381
|
-
const GOOSE_DIR = ".goose";
|
|
16382
|
-
const GOOSE_GLOBAL_DIR = (0, node_path.join)(".config", "goose");
|
|
16383
|
-
const GOOSE_RULE_FILE_NAME = ".goosehints";
|
|
16384
|
-
const GOOSE_MCP_FILE_NAME = "config.yaml";
|
|
16385
|
-
const GOOSE_PERMISSIONS_FILE_NAME = "permission.yaml";
|
|
16386
|
-
const GOOSE_HOOKS_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync", "hooks");
|
|
16387
|
-
const GOOSE_HOOKS_FILE_NAME = "hooks.json";
|
|
16388
|
-
const GOOSE_PLUGIN_MCP_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync");
|
|
16389
|
-
const GOOSE_PLUGIN_MCP_FILE_NAME = ".mcp.json";
|
|
16390
|
-
const GOOSE_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "skills");
|
|
16391
|
-
const GOOSE_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "skills");
|
|
16392
|
-
const GOOSE_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "recipes");
|
|
16393
|
-
const GOOSE_GLOBAL_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "recipes");
|
|
16394
|
-
const GOOSE_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "agents");
|
|
16395
|
-
const GOOSE_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "agents");
|
|
16396
|
-
//#endregion
|
|
16397
16479
|
//#region src/utils/tool-home.ts
|
|
16398
16480
|
/**
|
|
16399
16481
|
* Where the rulesync-side source files of a tool with a home override belong.
|
|
@@ -18115,27 +18197,71 @@ const CLINE_HOOKS_GLOBAL_DIR_PATH = (0, node_path.join)("Documents", "Cline", "H
|
|
|
18115
18197
|
const CLINE_HOOKS_MANIFEST_FILE_NAME = "rulesync-hooks.json";
|
|
18116
18198
|
//#endregion
|
|
18117
18199
|
//#region src/features/commands/cline-command.ts
|
|
18200
|
+
const CLINE_WORKFLOW_FIELD_TYPES = {
|
|
18201
|
+
name: "string",
|
|
18202
|
+
disabled: "boolean",
|
|
18203
|
+
enabled: "boolean"
|
|
18204
|
+
};
|
|
18205
|
+
function sanitizeClineWorkflowFrontmatter({ cline, relativeFilePath, logger }) {
|
|
18206
|
+
if (!isRecord$1(cline)) return {};
|
|
18207
|
+
return Object.fromEntries(Object.entries(cline).filter(([key, value]) => {
|
|
18208
|
+
const expected = Object.hasOwn(CLINE_WORKFLOW_FIELD_TYPES, key) ? CLINE_WORKFLOW_FIELD_TYPES[key] : void 0;
|
|
18209
|
+
if (expected === void 0 || typeof value === expected) return true;
|
|
18210
|
+
warnWithFallback(logger, `Cline commands: dropping cline.${key} from ${relativeFilePath}: Cline rejects the whole workflow unless it is a ${expected}.`);
|
|
18211
|
+
return false;
|
|
18212
|
+
}));
|
|
18213
|
+
}
|
|
18214
|
+
/**
|
|
18215
|
+
* Cline workflows (commands). A workflow may carry YAML frontmatter that Cline
|
|
18216
|
+
* reads: `name` (the slash-command name) and a `disabled` / `enabled` switch
|
|
18217
|
+
* (`parseWorkflowConfigFromMarkdown` in Cline's
|
|
18218
|
+
* `user-instruction-config-loader.ts`). The `cline:` section of a
|
|
18219
|
+
* `.rulesync/commands/*.md` file is written as that frontmatter, and every key
|
|
18220
|
+
* of an imported workflow's frontmatter is lifted back into `cline:`, so
|
|
18221
|
+
* hand-written workflow frontmatter survives an import → generate round trip.
|
|
18222
|
+
* Cline's own UI toggle is kept in Cline's state, not in the file. A workflow
|
|
18223
|
+
* without a `cline:` section is written as a bare body, as before.
|
|
18224
|
+
*
|
|
18225
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/extensions/config/user-instruction-config-loader.ts
|
|
18226
|
+
*/
|
|
18118
18227
|
var ClineCommand = class ClineCommand extends ToolCommand {
|
|
18119
18228
|
static getSettablePaths({ global } = {}) {
|
|
18120
18229
|
if (global) return { relativeDirPath: CLINE_COMMANDS_GLOBAL_DIR_PATH };
|
|
18121
18230
|
return { relativeDirPath: CLINE_COMMANDS_DIR_PATH };
|
|
18122
18231
|
}
|
|
18232
|
+
/** The workflow's own frontmatter, or an empty object for a bare body. */
|
|
18233
|
+
getClineFrontmatter() {
|
|
18234
|
+
const { frontmatter } = parseFrontmatter(this.getFileContent(), this.getFilePath());
|
|
18235
|
+
return isRecord$1(frontmatter) ? frontmatter : {};
|
|
18236
|
+
}
|
|
18123
18237
|
toRulesyncCommand() {
|
|
18238
|
+
const cline = this.getClineFrontmatter();
|
|
18239
|
+
const rulesyncFrontmatter = {
|
|
18240
|
+
targets: ["*"],
|
|
18241
|
+
...Object.keys(cline).length > 0 && { cline }
|
|
18242
|
+
};
|
|
18243
|
+
const body = this.getBody();
|
|
18124
18244
|
return new RulesyncCommand({
|
|
18125
18245
|
outputRoot: process.cwd(),
|
|
18126
|
-
frontmatter:
|
|
18127
|
-
body
|
|
18246
|
+
frontmatter: rulesyncFrontmatter,
|
|
18247
|
+
body,
|
|
18128
18248
|
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
18129
18249
|
relativeFilePath: this.relativeFilePath,
|
|
18130
|
-
fileContent:
|
|
18250
|
+
fileContent: stringifyFrontmatter(body, rulesyncFrontmatter),
|
|
18131
18251
|
validate: true
|
|
18132
18252
|
});
|
|
18133
18253
|
}
|
|
18134
|
-
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
18254
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false, logger }) {
|
|
18135
18255
|
const paths = this.getSettablePaths({ global });
|
|
18256
|
+
const cline = sanitizeClineWorkflowFrontmatter({
|
|
18257
|
+
cline: rulesyncCommand.getFrontmatter().cline,
|
|
18258
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
18259
|
+
logger
|
|
18260
|
+
});
|
|
18261
|
+
const body = rulesyncCommand.getBody();
|
|
18136
18262
|
return new ClineCommand({
|
|
18137
18263
|
outputRoot,
|
|
18138
|
-
fileContent:
|
|
18264
|
+
fileContent: Object.keys(cline).length > 0 ? stringifyFrontmatter(body, cline) : body,
|
|
18139
18265
|
relativeDirPath: paths.relativeDirPath,
|
|
18140
18266
|
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
18141
18267
|
validate
|
|
@@ -18148,7 +18274,7 @@ var ClineCommand = class ClineCommand extends ToolCommand {
|
|
|
18148
18274
|
};
|
|
18149
18275
|
}
|
|
18150
18276
|
getBody() {
|
|
18151
|
-
return this.getFileContent();
|
|
18277
|
+
return parseFrontmatter(this.getFileContent(), this.getFilePath()).body.trim();
|
|
18152
18278
|
}
|
|
18153
18279
|
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
18154
18280
|
return this.isTargetedByRulesyncCommandDefault({
|
|
@@ -18159,12 +18285,13 @@ var ClineCommand = class ClineCommand extends ToolCommand {
|
|
|
18159
18285
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
18160
18286
|
const paths = this.getSettablePaths({ global });
|
|
18161
18287
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
18162
|
-
const { body
|
|
18288
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
18289
|
+
const cline = isRecord$1(frontmatter) ? frontmatter : {};
|
|
18163
18290
|
return new ClineCommand({
|
|
18164
18291
|
outputRoot,
|
|
18165
18292
|
relativeDirPath: paths.relativeDirPath,
|
|
18166
18293
|
relativeFilePath,
|
|
18167
|
-
fileContent:
|
|
18294
|
+
fileContent: Object.keys(cline).length > 0 ? stringifyFrontmatter(body.trim(), cline) : body.trim(),
|
|
18168
18295
|
validate
|
|
18169
18296
|
});
|
|
18170
18297
|
}
|
|
@@ -32737,6 +32864,31 @@ const PI_PROMPT_BLOCKING_EVENT = "input";
|
|
|
32737
32864
|
* @see https://github.com/earendil-works/pi/blob/v0.87.1/packages/coding-agent/src/core/extensions/types.ts
|
|
32738
32865
|
*/
|
|
32739
32866
|
const PI_SETTLE_BLOCKING_EVENT = "agent_before_settle";
|
|
32867
|
+
/**
|
|
32868
|
+
* `session_stop` is oh-my-pi's stop surface (it has no `agent_before_settle`).
|
|
32869
|
+
* It fires when a main-session run is about to settle (never for subagents,
|
|
32870
|
+
* nor for a run the user aborted), and a handler returning
|
|
32871
|
+
* `{ continue: true, additionalContext }` queues one more turn with that text
|
|
32872
|
+
* as hidden model-visible context. As on Pi, only a stop command exiting with
|
|
32873
|
+
* code 2 asks for that, the feedback of every such command is combined, and
|
|
32874
|
+
* any other failure is rethrown for oh-my-pi to report. The generated handler
|
|
32875
|
+
* continues only when `event.stop_hook_active` is false, which oh-my-pi sets
|
|
32876
|
+
* after a continuation and clears once a stop pass lets the run settle, so the
|
|
32877
|
+
* agent is continued at most once per stop (the flag is shared by every
|
|
32878
|
+
* extension, so another extension's continuation also suppresses ours);
|
|
32879
|
+
* oh-my-pi additionally caps these
|
|
32880
|
+
* advisory continuations at 8 in a row. A run that ended on a provider error
|
|
32881
|
+
* (`stopReason: "error"`) is not continued, matching Pi's `completed`-only
|
|
32882
|
+
* gate. The advisory form is used rather than `{ decision: "block", reason }`,
|
|
32883
|
+
* which oh-my-pi keeps enforcing without that cap. oh-my-pi gives
|
|
32884
|
+
* `session_stop` handlers the 30-second extension handler budget: stop
|
|
32885
|
+
* commands running longer time the handler out, the run settles without a
|
|
32886
|
+
* continuation, and the command is not killed.
|
|
32887
|
+
*
|
|
32888
|
+
* @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/docs/extensions.md
|
|
32889
|
+
* @see https://github.com/can1357/oh-my-pi/blob/fa5ff4a3a977a28b93d025f4cf50f046e019f18c/packages/coding-agent/src/session/agent-session.ts
|
|
32890
|
+
*/
|
|
32891
|
+
const OMP_SESSION_STOP_BLOCKING_EVENT = "session_stop";
|
|
32740
32892
|
const PI_EXTENSION_DIALECT = {
|
|
32741
32893
|
overrideKey: "pi",
|
|
32742
32894
|
typesPackage: "@earendil-works/pi-coding-agent",
|
|
@@ -32758,7 +32910,8 @@ const OMP_EXTENSION_DIALECT = {
|
|
|
32758
32910
|
const PI_BLOCKING_MODE_BY_EVENT = {
|
|
32759
32911
|
[PI_BLOCKING_EVENT]: "tool",
|
|
32760
32912
|
[PI_PROMPT_BLOCKING_EVENT]: "prompt",
|
|
32761
|
-
[PI_SETTLE_BLOCKING_EVENT]: "settle"
|
|
32913
|
+
[PI_SETTLE_BLOCKING_EVENT]: "settle",
|
|
32914
|
+
[OMP_SESSION_STOP_BLOCKING_EVENT]: "sessionStop"
|
|
32762
32915
|
};
|
|
32763
32916
|
/**
|
|
32764
32917
|
* The body emitted inside a blocking handler's `catch`, per gate. `tool_call`
|
|
@@ -32771,7 +32924,8 @@ function failureLinesByMode(dialect) {
|
|
|
32771
32924
|
none: [],
|
|
32772
32925
|
tool: ["return { block: true, reason: toBlockReason(error) };"],
|
|
32773
32926
|
prompt: ["reportPromptGateFailure(ctx, toBlockReason(error));", dialect.promptCancelLine],
|
|
32774
|
-
settle: ["stopHookFailures.push(error);"]
|
|
32927
|
+
settle: ["stopHookFailures.push(error);"],
|
|
32928
|
+
sessionStop: ["stopHookFailures.push(error);"]
|
|
32775
32929
|
};
|
|
32776
32930
|
}
|
|
32777
32931
|
/**
|
|
@@ -32863,6 +33017,12 @@ const PROMPT_GATE_HELPER_LINES = [
|
|
|
32863
33017
|
" console.error(reason);",
|
|
32864
33018
|
"}"
|
|
32865
33019
|
];
|
|
33020
|
+
/** Helper emitted alongside every stop gate (Pi's settle gate and oh-my-pi's `session_stop`). */
|
|
33021
|
+
const STOP_HOOK_BLOCK_HELPER_LINES = [
|
|
33022
|
+
"function isStopHookBlock(error: unknown): boolean {",
|
|
33023
|
+
" return (error as { code?: unknown } | null)?.code === 2;",
|
|
33024
|
+
"}"
|
|
33025
|
+
];
|
|
32866
33026
|
/**
|
|
32867
33027
|
* Helper emitted alongside the stop hook's settle gate. `event.entries` holds
|
|
32868
33028
|
* the drafts earlier extensions proposed at this boundary, so they are kept and
|
|
@@ -32873,10 +33033,6 @@ const PROMPT_GATE_HELPER_LINES = [
|
|
|
32873
33033
|
* appended message is committed, and Pi re-validates the final context itself.
|
|
32874
33034
|
*/
|
|
32875
33035
|
const STOP_HOOK_HELPER_LINES = [
|
|
32876
|
-
"function isStopHookBlock(error: unknown): boolean {",
|
|
32877
|
-
" return (error as { code?: unknown } | null)?.code === 2;",
|
|
32878
|
-
"}",
|
|
32879
|
-
"",
|
|
32880
33036
|
"function continueAfterStopHook(",
|
|
32881
33037
|
" event: AgentBeforeSettleEvent,",
|
|
32882
33038
|
" reason: string,",
|
|
@@ -32959,14 +33115,16 @@ function buildSubscriptionLines(handlerGroups, dialect) {
|
|
|
32959
33115
|
const blocking = PI_BLOCKING_MODE_BY_EVENT[piEvent] ?? "none";
|
|
32960
33116
|
const isPromptGate = blocking === "prompt";
|
|
32961
33117
|
const isSettleGate = blocking === "settle";
|
|
33118
|
+
const isSessionStopGate = blocking === "sessionStop";
|
|
33119
|
+
const collectsStopFailures = isSettleGate || isSessionStopGate;
|
|
32962
33120
|
const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
|
|
32963
33121
|
const usesErrorFlag = handlers.some((h) => h.onlyOnError);
|
|
32964
33122
|
const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
|
|
32965
|
-
const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate ||
|
|
33123
|
+
const params = isPromptGate ? "event, ctx" : usesToolName || usesErrorFlag || gatesOnAssistant || isPromptGate || collectsStopFailures ? "event" : "";
|
|
32966
33124
|
lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${params}) => {`);
|
|
32967
33125
|
if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
|
|
32968
33126
|
if (isPromptGate) lines.push(` if (event.source === "extension") ${dialect.promptPassLine}`);
|
|
32969
|
-
if (
|
|
33127
|
+
if (collectsStopFailures) lines.push(" const stopHookFailures: unknown[] = [];");
|
|
32970
33128
|
for (const handler of handlers) lines.push(...buildCommandLines({
|
|
32971
33129
|
handler,
|
|
32972
33130
|
usesToolName,
|
|
@@ -32982,6 +33140,17 @@ function buildSubscriptionLines(handlerGroups, dialect) {
|
|
|
32982
33140
|
lines.push(" }");
|
|
32983
33141
|
lines.push(" if (stopHookFailures.length > 0) throw stopHookFailures[0];");
|
|
32984
33142
|
}
|
|
33143
|
+
if (isSessionStopGate) {
|
|
33144
|
+
lines.push(" const blocks = stopHookFailures.filter(isStopHookBlock);");
|
|
33145
|
+
lines.push(" const endedOnError = (event.last_assistant_message as { stopReason?: unknown } | undefined)?.stopReason === \"error\";");
|
|
33146
|
+
lines.push(" if (blocks.length > 0 && !event.stop_hook_active && !endedOnError) {");
|
|
33147
|
+
lines.push(" return {");
|
|
33148
|
+
lines.push(" continue: true,");
|
|
33149
|
+
lines.push(" additionalContext: `Stop hook feedback:\\n${blocks.map(toBlockReason).join(\"\\n\\n\")}`,");
|
|
33150
|
+
lines.push(" };");
|
|
33151
|
+
lines.push(" }");
|
|
33152
|
+
lines.push(" if (stopHookFailures.length > 0) throw stopHookFailures[0];");
|
|
33153
|
+
}
|
|
32985
33154
|
lines.push(" });");
|
|
32986
33155
|
if (isSettleGate) {
|
|
32987
33156
|
lines.push(` pi.on("agent_settled", async () => {`);
|
|
@@ -33000,8 +33169,9 @@ function buildSubscriptionLines(handlerGroups, dialect) {
|
|
|
33000
33169
|
* `{ block: true, reason }`, and on `input` — Pi's prompt-submission gate —
|
|
33001
33170
|
* where a non-zero exit cancels the prompt with the dialect's cancel result
|
|
33002
33171
|
* (`{ action: "handled" }` for Pi, `{ handled: true }` for oh-my-pi), and
|
|
33003
|
-
* on `agent_before_settle
|
|
33004
|
-
* agent to continue once with the
|
|
33172
|
+
* on `agent_before_settle` (Pi) / `session_stop` (oh-my-pi), where a stop
|
|
33173
|
+
* command exiting with code 2 asks the agent to continue once with the
|
|
33174
|
+
* command's output as feedback.
|
|
33005
33175
|
* `postToolUse` and `postToolUseFailure` share Pi's `tool_result` event; the
|
|
33006
33176
|
* latter's commands run only when `event.isError` is set.
|
|
33007
33177
|
*
|
|
@@ -33022,7 +33192,8 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
|
|
|
33022
33192
|
const subscriptionLines = buildSubscriptionLines(handlerGroups, dialect);
|
|
33023
33193
|
const hasPromptGate = Boolean(handlerGroups[PI_PROMPT_BLOCKING_EVENT]);
|
|
33024
33194
|
const hasSettleGate = Boolean(handlerGroups[PI_SETTLE_BLOCKING_EVENT]);
|
|
33025
|
-
const
|
|
33195
|
+
const hasSessionStopGate = Boolean(handlerGroups[OMP_SESSION_STOP_BLOCKING_EVENT]);
|
|
33196
|
+
const needsBlockReasonHelper = Boolean(handlerGroups[PI_BLOCKING_EVENT]) || hasPromptGate || hasSettleGate || hasSessionStopGate;
|
|
33026
33197
|
const lines = ["// Generated by rulesync. Do not edit manually."];
|
|
33027
33198
|
if (subscriptionLines.length === 0) {
|
|
33028
33199
|
lines.push("export default function () {}");
|
|
@@ -33049,6 +33220,10 @@ function generatePiExtensionCode({ config, supportedEvents, eventMap, dialect =
|
|
|
33049
33220
|
lines.push(...PROMPT_GATE_HELPER_LINES);
|
|
33050
33221
|
lines.push("");
|
|
33051
33222
|
}
|
|
33223
|
+
if (hasSettleGate || hasSessionStopGate) {
|
|
33224
|
+
lines.push(...STOP_HOOK_BLOCK_HELPER_LINES);
|
|
33225
|
+
lines.push("");
|
|
33226
|
+
}
|
|
33052
33227
|
if (hasSettleGate) {
|
|
33053
33228
|
lines.push(...STOP_HOOK_HELPER_LINES);
|
|
33054
33229
|
lines.push("");
|
|
@@ -40544,6 +40719,35 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
40544
40719
|
}
|
|
40545
40720
|
return result;
|
|
40546
40721
|
}
|
|
40722
|
+
/**
|
|
40723
|
+
* Keys Codex refuses on each transport. Its `McpServerConfig` deserializer
|
|
40724
|
+
* calls `throw_if_set` for these, and the error fails the whole `config.toml`,
|
|
40725
|
+
* not just the one server, so they are dropped with a warning instead of
|
|
40726
|
+
* written. Checked against `codex-rs/config/src/mcp_types.rs` at rust-v0.162.1.
|
|
40727
|
+
*/
|
|
40728
|
+
const CODEX_STDIO_REJECTED_KEYS = [
|
|
40729
|
+
"url",
|
|
40730
|
+
"bearer_token_env_var",
|
|
40731
|
+
"bearer_token",
|
|
40732
|
+
"http_headers_helper",
|
|
40733
|
+
"http_headers",
|
|
40734
|
+
"env_http_headers",
|
|
40735
|
+
"oauth",
|
|
40736
|
+
"oauth_resource",
|
|
40737
|
+
"auth"
|
|
40738
|
+
];
|
|
40739
|
+
const CODEX_HTTP_REJECTED_KEYS = [
|
|
40740
|
+
"args",
|
|
40741
|
+
"env",
|
|
40742
|
+
"env_vars",
|
|
40743
|
+
"cwd",
|
|
40744
|
+
"bearer_token"
|
|
40745
|
+
];
|
|
40746
|
+
function dropTransportRejectedKeys({ converted, isStdio, serverName }) {
|
|
40747
|
+
const dropped = (isStdio ? CODEX_STDIO_REJECTED_KEYS : CODEX_HTTP_REJECTED_KEYS).filter((key) => converted[key] !== void 0);
|
|
40748
|
+
for (const key of dropped) delete converted[key];
|
|
40749
|
+
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.`);
|
|
40750
|
+
}
|
|
40547
40751
|
function convertToCodexFormat(mcpServers) {
|
|
40548
40752
|
const result = {};
|
|
40549
40753
|
const originalNames = /* @__PURE__ */ new Map();
|
|
@@ -40586,6 +40790,11 @@ function convertToCodexFormat(mcpServers) {
|
|
|
40586
40790
|
converted,
|
|
40587
40791
|
serverName: name
|
|
40588
40792
|
});
|
|
40793
|
+
dropTransportRejectedKeys({
|
|
40794
|
+
converted,
|
|
40795
|
+
isStdio,
|
|
40796
|
+
serverName: name
|
|
40797
|
+
});
|
|
40589
40798
|
const previousName = originalNames.get(codexName);
|
|
40590
40799
|
if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
|
|
40591
40800
|
originalNames.set(codexName, name);
|
|
@@ -55811,6 +56020,124 @@ function parseCodebuddySettings({ fileContent, relativePath }) {
|
|
|
55811
56020
|
}
|
|
55812
56021
|
}
|
|
55813
56022
|
//#endregion
|
|
56023
|
+
//#region src/features/permissions/codewhale-project-config.ts
|
|
56024
|
+
/**
|
|
56025
|
+
* The top-level key of the per-project `.codewhale/config.toml` overlay that
|
|
56026
|
+
* hides Codewhale's shell tools. The overlay only tightens: `false` disables
|
|
56027
|
+
* shell for the session, while `true` is ignored with a warning.
|
|
56028
|
+
* @see https://github.com/Hmbown/Codewhale/blob/fead51eeea0fb6038ea3e30923751b1a1c765b25/docs/CONFIGURATION.md#per-project-overlay-485
|
|
56029
|
+
* @see https://github.com/Hmbown/Codewhale/blob/fead51eeea0fb6038ea3e30923751b1a1c765b25/crates/tui/src/lib.rs
|
|
56030
|
+
*/
|
|
56031
|
+
const CODEWHALE_ALLOW_SHELL_KEY = "allow_shell";
|
|
56032
|
+
const CODEWHALE_PROJECT_CONFIG_PATHS = {
|
|
56033
|
+
relativeDirPath: CODEWHALE_DIR,
|
|
56034
|
+
relativeFilePath: CODEWHALE_CONFIG_FILE_NAME
|
|
56035
|
+
};
|
|
56036
|
+
/** The project `.codewhale/config.toml` overlay, as written by the permissions feature. */
|
|
56037
|
+
var CodewhaleProjectConfigFile = class extends ToolFile {
|
|
56038
|
+
validate() {
|
|
56039
|
+
return {
|
|
56040
|
+
success: true,
|
|
56041
|
+
error: null
|
|
56042
|
+
};
|
|
56043
|
+
}
|
|
56044
|
+
};
|
|
56045
|
+
/**
|
|
56046
|
+
* Whether the canonical config denies every shell command: a `deny` on the
|
|
56047
|
+
* catch-all `*` (or `**`) pattern of `bash` or of the all-tools `*` category.
|
|
56048
|
+
* Codewhale decides by action before specificity, so such a deny already
|
|
56049
|
+
* blocks every command in global scope too, whatever `allow` rules sit beside
|
|
56050
|
+
* it.
|
|
56051
|
+
*/
|
|
56052
|
+
function deniesEveryShellCommand(permission) {
|
|
56053
|
+
return collectShellCommandRules(permission).rules.some(({ pattern, action }) => action === "deny" && isShellCatchAll(pattern));
|
|
56054
|
+
}
|
|
56055
|
+
/** Whether a canonical shell pattern names every command. */
|
|
56056
|
+
function isShellCatchAll(pattern) {
|
|
56057
|
+
const trimmed = pattern.trim();
|
|
56058
|
+
return trimmed === "*" || trimmed === "**";
|
|
56059
|
+
}
|
|
56060
|
+
function projectConfigFilePath(outputRoot) {
|
|
56061
|
+
return (0, node_path.join)(outputRoot, CODEWHALE_PROJECT_CONFIG_PATHS.relativeDirPath, CODEWHALE_PROJECT_CONFIG_PATHS.relativeFilePath);
|
|
56062
|
+
}
|
|
56063
|
+
function parseProjectConfig({ fileContent, filePath }) {
|
|
56064
|
+
return parseSharedConfig({
|
|
56065
|
+
format: "toml",
|
|
56066
|
+
fileContent,
|
|
56067
|
+
filePath,
|
|
56068
|
+
invalidRootPolicy: "error"
|
|
56069
|
+
});
|
|
56070
|
+
}
|
|
56071
|
+
/**
|
|
56072
|
+
* Whether an overlay disables shell (`allow_shell = false`). A file that
|
|
56073
|
+
* cannot be read as TOML is reported and treated as not disabling shell.
|
|
56074
|
+
*/
|
|
56075
|
+
function disablesShell({ fileContent, filePath, logger }) {
|
|
56076
|
+
try {
|
|
56077
|
+
return parseProjectConfig({
|
|
56078
|
+
fileContent,
|
|
56079
|
+
filePath
|
|
56080
|
+
})[CODEWHALE_ALLOW_SHELL_KEY] === false;
|
|
56081
|
+
} catch (error) {
|
|
56082
|
+
logger?.warn(`Codewhale permissions: not reading "allow_shell" from ${filePath}, so a shell deny set there is not taken into account: ${formatError(error)}`);
|
|
56083
|
+
return false;
|
|
56084
|
+
}
|
|
56085
|
+
}
|
|
56086
|
+
/**
|
|
56087
|
+
* Whether the project overlay at `outputRoot` disables shell
|
|
56088
|
+
* (`allow_shell = false`), for import.
|
|
56089
|
+
*/
|
|
56090
|
+
async function readCodewhaleProjectShellDisabled({ outputRoot, logger }) {
|
|
56091
|
+
const filePath = projectConfigFilePath(outputRoot);
|
|
56092
|
+
const fileContent = await readFileContentOrNull(filePath);
|
|
56093
|
+
if (fileContent === null) return false;
|
|
56094
|
+
return disablesShell({
|
|
56095
|
+
fileContent,
|
|
56096
|
+
filePath,
|
|
56097
|
+
logger
|
|
56098
|
+
});
|
|
56099
|
+
}
|
|
56100
|
+
/**
|
|
56101
|
+
* The project `.codewhale/config.toml` overlay carrying `allow_shell = false`
|
|
56102
|
+
* when the canonical config denies every shell command, or `null` when the
|
|
56103
|
+
* file needs no change.
|
|
56104
|
+
*
|
|
56105
|
+
* Only `allow_shell` is written; every other overlay key (`model`,
|
|
56106
|
+
* `approval_policy`, ...) is kept, and an existing file that is not valid TOML
|
|
56107
|
+
* is an error only when it has to be written. An existing
|
|
56108
|
+
* `allow_shell = false` is never removed: the overlay cannot widen access, and
|
|
56109
|
+
* a hand-written value cannot be told apart from one rulesync wrote, so it is
|
|
56110
|
+
* reported instead.
|
|
56111
|
+
*/
|
|
56112
|
+
async function createCodewhaleProjectConfigFile({ outputRoot = process.cwd(), config, logger }) {
|
|
56113
|
+
const filePath = projectConfigFilePath(outputRoot);
|
|
56114
|
+
const existingContent = await readFileContentOrNull(filePath);
|
|
56115
|
+
if (!deniesEveryShellCommand(config.permission)) {
|
|
56116
|
+
if (existingContent !== null && disablesShell({
|
|
56117
|
+
fileContent: existingContent,
|
|
56118
|
+
filePath,
|
|
56119
|
+
logger
|
|
56120
|
+
})) 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.`);
|
|
56121
|
+
return null;
|
|
56122
|
+
}
|
|
56123
|
+
if (parseProjectConfig({
|
|
56124
|
+
fileContent: existingContent ?? "",
|
|
56125
|
+
filePath
|
|
56126
|
+
})[CODEWHALE_ALLOW_SHELL_KEY] === false) return null;
|
|
56127
|
+
return new CodewhaleProjectConfigFile({
|
|
56128
|
+
outputRoot,
|
|
56129
|
+
...CODEWHALE_PROJECT_CONFIG_PATHS,
|
|
56130
|
+
fileContent: applySharedConfigPatch({
|
|
56131
|
+
fileKey: sharedConfigFileKey(CODEWHALE_PROJECT_CONFIG_PATHS),
|
|
56132
|
+
feature: "permissions",
|
|
56133
|
+
existingContent: existingContent ?? "",
|
|
56134
|
+
patch: { [CODEWHALE_ALLOW_SHELL_KEY]: false },
|
|
56135
|
+
filePath,
|
|
56136
|
+
logger
|
|
56137
|
+
})
|
|
56138
|
+
});
|
|
56139
|
+
}
|
|
56140
|
+
//#endregion
|
|
55814
56141
|
//#region src/features/permissions/codewhale-repo-law.ts
|
|
55815
56142
|
/**
|
|
55816
56143
|
* Project-scope Codewhale permissions: the enforced `protected_invariants` of
|
|
@@ -55961,7 +56288,8 @@ function collectRestrictions({ config, logger }) {
|
|
|
55961
56288
|
let skippedAllowCount = 0;
|
|
55962
56289
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
55963
56290
|
if (!REPO_LAW_CATEGORIES.includes(category)) {
|
|
55964
|
-
|
|
56291
|
+
const entries = Object.entries(rules);
|
|
56292
|
+
if ((category === "bash" && entries.some(([pattern, action]) => action === "deny" && isShellCatchAll(pattern)) ? entries.filter(([, action]) => action === "allow") : entries).length > 0) skippedCategories.push(category);
|
|
55965
56293
|
continue;
|
|
55966
56294
|
}
|
|
55967
56295
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -56687,17 +57015,32 @@ async function warnOnShadowedConstitution({ outputRoot, logger }) {
|
|
|
56687
57015
|
* Project scope manages the enforced `protected_invariants` of the repo
|
|
56688
57016
|
* constitution `.codewhale/constitution.json` (see `codewhale-repo-law.ts`):
|
|
56689
57017
|
* the project `.codewhale/config.toml` overlay cannot carry rules, but repo
|
|
56690
|
-
* law can hold writes to protected paths.
|
|
57018
|
+
* law can hold writes to protected paths. A deny on every shell command also
|
|
57019
|
+
* sets `allow_shell = false` in that overlay (see
|
|
57020
|
+
* `codewhale-project-config.ts`), which import reads back here.
|
|
56691
57021
|
*
|
|
56692
57022
|
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/CONFIGURATION.md
|
|
56693
57023
|
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/AUTHORIZATION_ORDER.md
|
|
56694
57024
|
*/
|
|
56695
57025
|
var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
|
|
56696
|
-
|
|
57026
|
+
/**
|
|
57027
|
+
* Whether the project `.codewhale/config.toml` overlay disables shell
|
|
57028
|
+
* (`allow_shell = false`); read on import in project scope only.
|
|
57029
|
+
*/
|
|
57030
|
+
projectShellDisabled;
|
|
57031
|
+
constructor({ projectShellDisabled = false, ...params }) {
|
|
56697
57032
|
super({
|
|
56698
57033
|
...params,
|
|
56699
57034
|
fileContent: params.fileContent ?? ""
|
|
56700
57035
|
});
|
|
57036
|
+
this.projectShellDisabled = projectShellDisabled;
|
|
57037
|
+
}
|
|
57038
|
+
/**
|
|
57039
|
+
* The project `.codewhale/config.toml` overlay, where a deny on every shell
|
|
57040
|
+
* command becomes `allow_shell = false` beside the user's other overlay keys.
|
|
57041
|
+
*/
|
|
57042
|
+
static getExtraSharedWritePaths(options) {
|
|
57043
|
+
return options?.global ? [] : [{ ...CODEWHALE_PROJECT_CONFIG_PATHS }];
|
|
56701
57044
|
}
|
|
56702
57045
|
/**
|
|
56703
57046
|
* Codewhale's approval card writes to the global file, and the constitution
|
|
@@ -56734,7 +57077,11 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
|
|
|
56734
57077
|
relativeFilePath: paths.relativeFilePath,
|
|
56735
57078
|
fileContent,
|
|
56736
57079
|
validate,
|
|
56737
|
-
global
|
|
57080
|
+
global,
|
|
57081
|
+
projectShellDisabled: global ? false : await readCodewhaleProjectShellDisabled({
|
|
57082
|
+
outputRoot,
|
|
57083
|
+
logger
|
|
57084
|
+
})
|
|
56738
57085
|
});
|
|
56739
57086
|
}
|
|
56740
57087
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
@@ -56796,11 +57143,15 @@ var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
|
|
|
56796
57143
|
toRulesyncPermissions() {
|
|
56797
57144
|
const filePath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
56798
57145
|
if (!this.global) {
|
|
56799
|
-
const
|
|
57146
|
+
const permission = codewhaleConstitutionToCanonical(parseCodewhaleConstitution({
|
|
56800
57147
|
fileContent: this.getFileContent(),
|
|
56801
57148
|
filePath
|
|
56802
|
-
});
|
|
56803
|
-
|
|
57149
|
+
}));
|
|
57150
|
+
if (this.projectShellDisabled) permission.bash = {
|
|
57151
|
+
...permission.bash,
|
|
57152
|
+
"*": "deny"
|
|
57153
|
+
};
|
|
57154
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
56804
57155
|
}
|
|
56805
57156
|
const { permission, nativeRules } = codewhaleRulesToCanonical(partitionRuleRecords(parsePermissionsDocument({
|
|
56806
57157
|
fileContent: this.getFileContent(),
|
|
@@ -67806,10 +68157,15 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
67806
68157
|
outputRoot,
|
|
67807
68158
|
logger
|
|
67808
68159
|
});
|
|
68160
|
+
const inheritedProvider = global ? void 0 : await readGlobalTaktProvider({
|
|
68161
|
+
projectFilePath: filePath,
|
|
68162
|
+
logger
|
|
68163
|
+
});
|
|
67809
68164
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
67810
68165
|
const provider = resolveActiveProvider({
|
|
67811
68166
|
config,
|
|
67812
|
-
runtime: runtime.active ? runtime.config : void 0
|
|
68167
|
+
runtime: runtime.active ? runtime.config : void 0,
|
|
68168
|
+
inheritedProvider
|
|
67813
68169
|
});
|
|
67814
68170
|
const mode = deriveTaktPermissionMode(rulesyncJson);
|
|
67815
68171
|
const override = isPlainObject$1(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
|
|
@@ -67919,10 +68275,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
67919
68275
|
* `provider_profiles` (unless that key is the pre-0.68.0 `claude` fallback),
|
|
67920
68276
|
* else the `claude-sdk` default (Takt 0.68.0+).
|
|
67921
68277
|
*/
|
|
67922
|
-
function resolveActiveProvider({ config, runtime }) {
|
|
68278
|
+
function resolveActiveProvider({ config, runtime, inheritedProvider }) {
|
|
67923
68279
|
const fromRuntime = runtime === void 0 ? void 0 : resolveRuntimeProvider(runtime);
|
|
67924
68280
|
if (fromRuntime !== void 0) return fromRuntime;
|
|
67925
|
-
|
|
68281
|
+
const local = readProviderName(config);
|
|
68282
|
+
if (local !== void 0) return local;
|
|
68283
|
+
if (inheritedProvider !== void 0) return inheritedProvider;
|
|
67926
68284
|
const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
|
|
67927
68285
|
if (isPlainObject$1(profiles)) {
|
|
67928
68286
|
const keys = Object.keys(profiles);
|
|
@@ -67930,6 +68288,43 @@ function resolveActiveProvider({ config, runtime }) {
|
|
|
67930
68288
|
}
|
|
67931
68289
|
return TAKT_DEFAULT_PROVIDER;
|
|
67932
68290
|
}
|
|
68291
|
+
/**
|
|
68292
|
+
* The top-level `provider:` of a Takt `config.yaml`, when it names one: either
|
|
68293
|
+
* the plain name or the block form `provider: { type: <name>, ... }` Takt also
|
|
68294
|
+
* accepts (`ConfigProviderReference`).
|
|
68295
|
+
*/
|
|
68296
|
+
function readProviderName(config) {
|
|
68297
|
+
const raw = config[TAKT_PROVIDER_KEY];
|
|
68298
|
+
const provider = isPlainObject$1(raw) ? raw.type : raw;
|
|
68299
|
+
return typeof provider === "string" && provider.trim() !== "" ? provider : void 0;
|
|
68300
|
+
}
|
|
68301
|
+
/**
|
|
68302
|
+
* The top-level `provider:` of the global `~/.takt/config.yaml`, read for a
|
|
68303
|
+
* project-scope generate. An unresolvable home directory, a missing or
|
|
68304
|
+
* unparsable file, or one that names no provider all mean "nothing inherited".
|
|
68305
|
+
*/
|
|
68306
|
+
async function readGlobalTaktProvider({ projectFilePath, logger }) {
|
|
68307
|
+
let globalPath;
|
|
68308
|
+
try {
|
|
68309
|
+
globalPath = (0, node_path.join)(getHomeDirectory(), TAKT_DIR, TAKT_CONFIG_FILE_NAME);
|
|
68310
|
+
} catch {
|
|
68311
|
+
return;
|
|
68312
|
+
}
|
|
68313
|
+
if (globalPath === projectFilePath) return;
|
|
68314
|
+
try {
|
|
68315
|
+
const content = await readFileContentOrNull(globalPath);
|
|
68316
|
+
if (content === null) return;
|
|
68317
|
+
return readProviderName(parseSharedConfig({
|
|
68318
|
+
format: "yaml",
|
|
68319
|
+
fileContent: content,
|
|
68320
|
+
filePath: globalPath,
|
|
68321
|
+
invalidRootPolicy: "error"
|
|
68322
|
+
}));
|
|
68323
|
+
} catch (error) {
|
|
68324
|
+
logger?.warn(`Takt permissions: could not read the provider from ${globalPath} (${formatError(error)}); falling back to the project config alone.`);
|
|
68325
|
+
return;
|
|
68326
|
+
}
|
|
68327
|
+
}
|
|
67933
68328
|
/** A plain object carrying at least one entry (Takt's "assignment" test). */
|
|
67934
68329
|
function hasEntries(value) {
|
|
67935
68330
|
return isPlainObject$1(value) && Object.keys(value).length > 0;
|
|
@@ -71685,6 +72080,14 @@ var PermissionsProcessor = class extends FeatureProcessor {
|
|
|
71685
72080
|
logger: this.logger,
|
|
71686
72081
|
global: this.global
|
|
71687
72082
|
});
|
|
72083
|
+
if (this.toolTarget === "codewhale" && !this.global) {
|
|
72084
|
+
const projectConfigFile = await createCodewhaleProjectConfigFile({
|
|
72085
|
+
outputRoot: this.outputRoot,
|
|
72086
|
+
config: effectivePermissions.getJson(),
|
|
72087
|
+
logger: this.logger
|
|
72088
|
+
});
|
|
72089
|
+
return projectConfigFile ? [toolPermissions, projectConfigFile] : [toolPermissions];
|
|
72090
|
+
}
|
|
71688
72091
|
if (this.toolTarget !== "codexcli") return [toolPermissions];
|
|
71689
72092
|
return [toolPermissions, createCodexcliBashRulesFile({
|
|
71690
72093
|
outputRoot: this.outputRoot,
|