rulesync 26.0.0 → 27.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 +5 -5
- package/dist/cli/index.cjs +10 -5
- package/dist/cli/index.js +10 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CGGCq_jP.cjs → import-B8FW-x0a.cjs} +1952 -434
- package/dist/{import-CFKjn6d-.js → import-CnhfUGo7.js} +1931 -419
- package/dist/import-CnhfUGo7.js.map +1 -0
- 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
- package/dist/import-CFKjn6d-.js.map +0 -1
|
@@ -444,36 +444,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
444
444
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
445
445
|
//#endregion
|
|
446
446
|
//#region src/constants/rulesync-paths.ts
|
|
447
|
-
const { join: join$
|
|
447
|
+
const { join: join$410 } = node_path.posix;
|
|
448
448
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
449
449
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
450
450
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
451
451
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
452
452
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
453
453
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
454
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
454
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$410(RULES_FEATURE_SUBDIR, ".curated");
|
|
455
455
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
456
456
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
457
457
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
458
458
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
459
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
460
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
461
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
462
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
463
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
464
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
465
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
466
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
467
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
468
|
-
join$
|
|
469
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
470
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
459
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$410(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
460
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
461
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
462
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
463
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
464
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
465
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
466
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
467
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
468
|
+
join$410(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
469
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
470
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
471
471
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
472
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
472
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
473
473
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
474
474
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
475
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
476
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
475
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$410(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
477
477
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
478
478
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
479
479
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -712,6 +712,7 @@ const subagentsProcessorToolTargetTuple = [
|
|
|
712
712
|
"antigravity-plugin",
|
|
713
713
|
"augmentcode",
|
|
714
714
|
"augmentcode-plugin",
|
|
715
|
+
"bob",
|
|
715
716
|
"claudecode",
|
|
716
717
|
"claudecode-plugin",
|
|
717
718
|
"claudecode-legacy",
|
|
@@ -878,6 +879,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
878
879
|
"claudecode",
|
|
879
880
|
"cline",
|
|
880
881
|
"codebuddy",
|
|
882
|
+
"codewhale",
|
|
881
883
|
"codexcli",
|
|
882
884
|
"commandcode",
|
|
883
885
|
"continue",
|
|
@@ -899,6 +901,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
899
901
|
"kiro-ide",
|
|
900
902
|
"lettacode",
|
|
901
903
|
"mimocode",
|
|
904
|
+
"omp",
|
|
902
905
|
"opencode",
|
|
903
906
|
"pi",
|
|
904
907
|
"pool",
|
|
@@ -922,6 +925,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
922
925
|
"factorydroid",
|
|
923
926
|
"gitlabduo",
|
|
924
927
|
"hermesagent",
|
|
928
|
+
"kilo",
|
|
925
929
|
"qwencode",
|
|
926
930
|
"rovodev",
|
|
927
931
|
"takt"
|
|
@@ -3716,6 +3720,8 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
3716
3720
|
".grok/sandbox.toml",
|
|
3717
3721
|
".letta/settings.json",
|
|
3718
3722
|
".qoder/settings.json",
|
|
3723
|
+
".omp/config.yml",
|
|
3724
|
+
".omp/agent/config.yml",
|
|
3719
3725
|
".rovodev/config.yml",
|
|
3720
3726
|
".rovodev/mcp.json",
|
|
3721
3727
|
".poolside/settings.yaml",
|
|
@@ -7689,6 +7695,32 @@ const ToolScopedPermissionSchema = zod_mini.z.record(PermissionCategorySchema, P
|
|
|
7689
7695
|
* added without a schema break.
|
|
7690
7696
|
*/
|
|
7691
7697
|
const CanonicalPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(ToolScopedPermissionSchema) });
|
|
7698
|
+
/**
|
|
7699
|
+
* One Codewhale `[[rules]]` record of `~/.codewhale/permissions.toml`, written
|
|
7700
|
+
* verbatim. Codewhale parses the file with unknown fields denied, so a single
|
|
7701
|
+
* misspelled key would make it reject every rule in the file; the record is
|
|
7702
|
+
* therefore strict here, surfacing the typo on the rulesync source instead.
|
|
7703
|
+
*
|
|
7704
|
+
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/CONFIGURATION.md
|
|
7705
|
+
*/
|
|
7706
|
+
const CodewhalePermissionRuleSchema = zod_mini.z.strictObject({
|
|
7707
|
+
tool: zod_mini.z.string(),
|
|
7708
|
+
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
7709
|
+
command_exact: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7710
|
+
path: zod_mini.z.optional(zod_mini.z.string()),
|
|
7711
|
+
workspace: zod_mini.z.optional(zod_mini.z.string()),
|
|
7712
|
+
action: zod_mini.z.optional(PermissionActionSchema)
|
|
7713
|
+
});
|
|
7714
|
+
/**
|
|
7715
|
+
* Tool-scoped override block for Codewhale. `rules` carries native records the
|
|
7716
|
+
* canonical category/pattern shape cannot express — a `workspace`-scoped rule,
|
|
7717
|
+
* a tool rulesync does not map, an exact-command deny — and is emitted ahead of
|
|
7718
|
+
* the rules converted from the canonical block.
|
|
7719
|
+
*/
|
|
7720
|
+
const CodewhalePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
7721
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7722
|
+
rules: zod_mini.z.optional(zod_mini.z.array(CodewhalePermissionRuleSchema))
|
|
7723
|
+
});
|
|
7692
7724
|
const KimiCodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
7693
7725
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7694
7726
|
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum([
|
|
@@ -7909,12 +7941,15 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
7909
7941
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
7910
7942
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
7911
7943
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
7912
|
-
* `todoWrite`, `workflowsEnabled`
|
|
7913
|
-
* `
|
|
7914
|
-
* `tools.workflowsEnabled`,
|
|
7944
|
+
* `todoWrite`, `workflowsEnabled`, `workflowNameOnly`, `executionSandbox`) and
|
|
7945
|
+
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
7946
|
+
* `allowedInsecureVoiceBaseUrls`). Qwen Code strips `tools.workflowsEnabled`,
|
|
7947
|
+
* `tools.executionSandbox`, `security.allowPrivateNetworkHooks` and
|
|
7915
7948
|
* `security.allowedInsecureVoiceBaseUrls` out of workspace settings, so generate
|
|
7916
|
-
* skips those
|
|
7917
|
-
* scope
|
|
7949
|
+
* skips those four in project scope and announces a granting value in global
|
|
7950
|
+
* scope (any `executionSandbox` change counts). `tools.workflowNameOnly` is
|
|
7951
|
+
* written in both scopes, but a workspace may only turn it on, which the
|
|
7952
|
+
* project-scope note says. `security.allowedHttpHookUrls` (honored in a workspace only while no
|
|
7918
7953
|
* higher scope sets it) and `security.folderTrust` (the initial trust decision
|
|
7919
7954
|
* is made from user/system settings alone, before the workspace merge) are
|
|
7920
7955
|
* written in both scopes, with a note in project scope and an announcement of
|
|
@@ -8802,6 +8837,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
8802
8837
|
augmentcode: zod_mini.z.optional(AugmentcodePermissionsOverrideSchema),
|
|
8803
8838
|
kiro: zod_mini.z.optional(KiroPermissionsOverrideSchema),
|
|
8804
8839
|
codexcli: zod_mini.z.optional(CodexcliPermissionsOverrideSchema),
|
|
8840
|
+
codewhale: zod_mini.z.optional(CodewhalePermissionsOverrideSchema),
|
|
8805
8841
|
zed: zod_mini.z.optional(ZedPermissionsOverrideSchema),
|
|
8806
8842
|
devin: zod_mini.z.optional(DevinPermissionsOverrideSchema),
|
|
8807
8843
|
"antigravity-ide": zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -8814,6 +8850,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
8814
8850
|
copilotcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
8815
8851
|
crush: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
8816
8852
|
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
8853
|
+
omp: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
8817
8854
|
pool: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
8818
8855
|
qoder: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
8819
8856
|
grokcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -10747,6 +10784,13 @@ const RulesyncSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
10747
10784
|
customInstructions: zod_mini.z.optional(zod_mini.z.string()),
|
|
10748
10785
|
groups: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.unknown()))
|
|
10749
10786
|
})),
|
|
10787
|
+
bob: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10788
|
+
slug: zod_mini.z.optional(zod_mini.z.string()),
|
|
10789
|
+
whenToUse: zod_mini.z.optional(zod_mini.z.string()),
|
|
10790
|
+
roleDefinition: zod_mini.z.optional(zod_mini.z.string()),
|
|
10791
|
+
customInstructions: zod_mini.z.optional(zod_mini.z.string()),
|
|
10792
|
+
groups: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.unknown()))
|
|
10793
|
+
})),
|
|
10750
10794
|
zoocode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10751
10795
|
/** Per-mode MCP server allowlist (Zoo Code v3.60.0+); omitted = all. */
|
|
10752
10796
|
allowedMcpServers: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) })),
|
|
@@ -11087,6 +11131,27 @@ const GITLABDUO_SKILLS_DIR = "skills";
|
|
|
11087
11131
|
/** User skills (requires `--enable-global-skills` / `GITLAB_ENABLE_GLOBAL_SKILLS`). */
|
|
11088
11132
|
const GITLABDUO_GLOBAL_SKILLS_DIR = (0, node_path.join)(GITLABDUO_DIR, "skills");
|
|
11089
11133
|
//#endregion
|
|
11134
|
+
//#region src/constants/kilo-paths.ts
|
|
11135
|
+
const KILO_DIR = ".kilo";
|
|
11136
|
+
const KILO_GLOBAL_DIR = (0, node_path.join)(".config", "kilo");
|
|
11137
|
+
const KILO_RULES_DIR_NAME = "rules";
|
|
11138
|
+
const KILO_COMMANDS_DIR_PATH = (0, node_path.join)(KILO_DIR, "commands");
|
|
11139
|
+
const KILO_GLOBAL_COMMANDS_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "commands");
|
|
11140
|
+
const KILO_SKILLS_DIR_PATH = (0, node_path.join)(KILO_DIR, "skills");
|
|
11141
|
+
const KILO_AGENTS_DIR_PATH = (0, node_path.join)(KILO_DIR, "agents");
|
|
11142
|
+
const KILO_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "agents");
|
|
11143
|
+
const KILO_PLUGINS_DIR_PATH = (0, node_path.join)(KILO_DIR, "plugins");
|
|
11144
|
+
const KILO_GLOBAL_PLUGINS_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "plugins");
|
|
11145
|
+
const KILO_PLUGIN_DIR_PATH = (0, node_path.join)(KILO_DIR, "plugin");
|
|
11146
|
+
const KILO_GLOBAL_PLUGIN_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "plugin");
|
|
11147
|
+
const KILO_RULE_FILE_NAME = "AGENTS.md";
|
|
11148
|
+
const KILO_IGNORE_FILE_NAME = ".kilocodeignore";
|
|
11149
|
+
const KILO_LEGACY_GLOBAL_DIR = ".kilocode";
|
|
11150
|
+
const KILO_JSONC_FILE_NAME = "kilo.jsonc";
|
|
11151
|
+
const KILO_JSON_FILE_NAME = "kilo.json";
|
|
11152
|
+
const KILO_HOOKS_FILE_NAME = "rulesync-hooks.js";
|
|
11153
|
+
const KILO_REVIEW_FILE_NAME = "REVIEW.md";
|
|
11154
|
+
//#endregion
|
|
11090
11155
|
//#region src/constants/qwencode-paths.ts
|
|
11091
11156
|
const QWENCODE_DIR = ".qwen";
|
|
11092
11157
|
const QWENCODE_COMMANDS_DIR_PATH = (0, node_path.join)(QWENCODE_DIR, "commands");
|
|
@@ -11478,8 +11543,14 @@ var FeatureProcessor = class extends RulesyncSourceConsumer {
|
|
|
11478
11543
|
async removeOrphanAiFiles(existingFiles, generatedFiles) {
|
|
11479
11544
|
const generatedPaths = new Set(generatedFiles.map((f) => caseFoldIdentity(f.getFilePath())));
|
|
11480
11545
|
const orphanFiles = existingFiles.filter((f) => !generatedPaths.has(caseFoldIdentity(f.getFilePath())));
|
|
11546
|
+
let removedCount = 0;
|
|
11481
11547
|
for (const aiFile of orphanFiles) {
|
|
11482
11548
|
const filePath = aiFile.getFilePath();
|
|
11549
|
+
if (await refusesDeleteOutsideRoot({
|
|
11550
|
+
logger: this.logger,
|
|
11551
|
+
rootPath: aiFile.getOutputRoot(),
|
|
11552
|
+
filePath
|
|
11553
|
+
})) continue;
|
|
11483
11554
|
const loggedPath = stripControlCharacters(filePath);
|
|
11484
11555
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would delete: ${loggedPath}`);
|
|
11485
11556
|
else {
|
|
@@ -11490,8 +11561,9 @@ var FeatureProcessor = class extends RulesyncSourceConsumer {
|
|
|
11490
11561
|
path: aiFile.getRelativePathFromCwd(),
|
|
11491
11562
|
kind: "file"
|
|
11492
11563
|
});
|
|
11564
|
+
removedCount++;
|
|
11493
11565
|
}
|
|
11494
|
-
return
|
|
11566
|
+
return removedCount;
|
|
11495
11567
|
}
|
|
11496
11568
|
/**
|
|
11497
11569
|
* The paths {@link removeOrphanAiFiles} deleted, or would have deleted under
|
|
@@ -11599,6 +11671,25 @@ async function refusesWriteOutsideRoot({ logger, rootPath, targetPath }) {
|
|
|
11599
11671
|
return true;
|
|
11600
11672
|
}
|
|
11601
11673
|
/**
|
|
11674
|
+
* Whether deleting `filePath` has to be refused because a link on the way to
|
|
11675
|
+
* the directory holding it leads out of `rootPath`, warning about the refusal.
|
|
11676
|
+
*
|
|
11677
|
+
* Deletion candidates are enumerated without following links *inside* the
|
|
11678
|
+
* scanned directory, but the scanned directory itself is reached through
|
|
11679
|
+
* whatever links its path contains: a checked-out repository can carry
|
|
11680
|
+
* `.cursor/commands -> ~/notes`, and every file in `~/notes` would then read as
|
|
11681
|
+
* an orphan. Only the parent is judged, because removing a file that is itself
|
|
11682
|
+
* a link unlinks the link where it is spelled and never touches its target.
|
|
11683
|
+
*/
|
|
11684
|
+
async function refusesDeleteOutsideRoot({ logger, rootPath, filePath }) {
|
|
11685
|
+
if (!await writablePathEscapesRoot({
|
|
11686
|
+
rootPath,
|
|
11687
|
+
targetPath: (0, node_path.dirname)(filePath)
|
|
11688
|
+
})) return false;
|
|
11689
|
+
logger.warn(`Refusing to delete ${quoteForLog(filePath)}: it resolves outside ${quoteForLog(rootPath)} through a symbolic link`);
|
|
11690
|
+
return true;
|
|
11691
|
+
}
|
|
11692
|
+
/**
|
|
11602
11693
|
* {@link refusesWriteOutsideRoot} over every path a single unit of output
|
|
11603
11694
|
* writes, stopping at the first refusal: a directory written as one unit is
|
|
11604
11695
|
* held back whole when any one file in it would land outside the root.
|
|
@@ -12741,7 +12832,7 @@ function stripSkillFrontmatter(fileContent) {
|
|
|
12741
12832
|
content = rest.slice(closing.index + closing[0].length).replace(LEADING_BLANK_LINE_PATTERN, "");
|
|
12742
12833
|
}
|
|
12743
12834
|
}
|
|
12744
|
-
function handWrittenWarning({ filePath, displayName, toolTarget }) {
|
|
12835
|
+
function handWrittenWarning$1({ filePath, displayName, toolTarget }) {
|
|
12745
12836
|
return `${displayName} checks: ${filePath} holds instructions rulesync did not write, so it is left as it is and no checks were generated for ${displayName}. Run \`rulesync import --targets ${toolTarget} --features checks\` to bring them into \`.rulesync/checks/\` and then delete the file, so the next generate writes it back from there; delete it outright if you no longer want it, or rename the directory if it is an ordinary skill rather than review guidelines. Importing alone leaves this file as it is, so it keeps blocking generation until it is gone. A rulesync skill named \`${FACTORYDROID_REVIEW_GUIDELINES_DIR_NAME}\` is no longer generated here — Factory's reviewer reads this path, so the checks feature owns it — but a directory an older rulesync wrote can still be sitting there.`;
|
|
12746
12837
|
}
|
|
12747
12838
|
/**
|
|
@@ -12798,7 +12889,7 @@ var FactorydroidCheck = class extends AggregatedToolCheck {
|
|
|
12798
12889
|
toolTarget: "factorydroid",
|
|
12799
12890
|
fallbackCheckName: FACTORYDROID_REVIEW_GUIDELINES_DIR_NAME,
|
|
12800
12891
|
handWrittenPreamble: "skip",
|
|
12801
|
-
handWrittenWarning,
|
|
12892
|
+
handWrittenWarning: handWrittenWarning$1,
|
|
12802
12893
|
transformImportedContent: stripSkillFrontmatter
|
|
12803
12894
|
};
|
|
12804
12895
|
}
|
|
@@ -13279,6 +13370,51 @@ var HermesagentCheck = class HermesagentCheck extends ToolCheck {
|
|
|
13279
13370
|
}
|
|
13280
13371
|
};
|
|
13281
13372
|
//#endregion
|
|
13373
|
+
//#region src/features/checks/kilo-check.ts
|
|
13374
|
+
function handWrittenWarning({ filePath, displayName, toolTarget }) {
|
|
13375
|
+
return `${displayName} checks: ${filePath} holds instructions rulesync did not write, so it is left as it is and no checks were generated for ${displayName}. Run \`rulesync import --targets ${toolTarget} --features checks\` to bring them into \`.rulesync/checks/\` and then delete the file, so the next generate writes it back from there. Importing alone leaves this file as it is, so it keeps blocking generation until it is gone.`;
|
|
13376
|
+
}
|
|
13377
|
+
/**
|
|
13378
|
+
* Checks adapter for Kilo Code Reviews' repository guidance (root `REVIEW.md`).
|
|
13379
|
+
*
|
|
13380
|
+
* The Kilo review agent reads one plain-Markdown file at the repository root
|
|
13381
|
+
* (from the PR/MR base branch, once "Use REVIEW.md" is enabled in the Kilo web
|
|
13382
|
+
* app), so every `.rulesync/checks/*.md` targeting Kilo collapses into it via
|
|
13383
|
+
* the `fromRulesyncChecks` on {@link AggregatedToolCheck}, with each check
|
|
13384
|
+
* written as a marked section (see `aggregated-check-file.ts` for the marker
|
|
13385
|
+
* convention).
|
|
13386
|
+
*
|
|
13387
|
+
* The reviewer reads the file as free prose, so a check's `severity` and
|
|
13388
|
+
* `tools` have no equivalent there: they are not written and do not come back
|
|
13389
|
+
* on import. Neither does `description` whenever the check also has a body.
|
|
13390
|
+
*
|
|
13391
|
+
* Project scope only — the reviewer reads the file from the repository and
|
|
13392
|
+
* there is no user-level equivalent.
|
|
13393
|
+
*
|
|
13394
|
+
* A root-level `REVIEW.md` is a generic name other reviewers may read too, so
|
|
13395
|
+
* unlike Cursor Bugbot's tool-specific `.cursor/BUGBOT.md` a hand-written file
|
|
13396
|
+
* here is left untouched (the `skip` policy) rather than replaced.
|
|
13397
|
+
*
|
|
13398
|
+
* @see https://kilo.ai/docs/automate/code-reviews
|
|
13399
|
+
*/
|
|
13400
|
+
var KiloCheck = class extends AggregatedToolCheck {
|
|
13401
|
+
static getSettablePaths(_options = {}) {
|
|
13402
|
+
return {
|
|
13403
|
+
relativeDirPath: ".",
|
|
13404
|
+
relativeFilePath: KILO_REVIEW_FILE_NAME
|
|
13405
|
+
};
|
|
13406
|
+
}
|
|
13407
|
+
static getAggregatedCheckConfig() {
|
|
13408
|
+
return {
|
|
13409
|
+
displayName: "Kilo Code",
|
|
13410
|
+
toolTarget: "kilo",
|
|
13411
|
+
fallbackCheckName: "review",
|
|
13412
|
+
handWrittenPreamble: "skip",
|
|
13413
|
+
handWrittenWarning
|
|
13414
|
+
};
|
|
13415
|
+
}
|
|
13416
|
+
};
|
|
13417
|
+
//#endregion
|
|
13282
13418
|
//#region src/features/checks/qwencode-check.ts
|
|
13283
13419
|
/**
|
|
13284
13420
|
* Checks adapter for Qwen Code's `/review` project rules (`.qwen/review-rules.md`).
|
|
@@ -13375,6 +13511,12 @@ const CLAUDECODE_LOCAL_RULE_FILE_NAME = "CLAUDE.local.md";
|
|
|
13375
13511
|
const CLAUDECODE_RULES_DIR_NAME = "rules";
|
|
13376
13512
|
/** Memories directory name under `.claude/` (legacy format). */
|
|
13377
13513
|
const CLAUDECODE_MEMORIES_DIR_NAME = "memories";
|
|
13514
|
+
/**
|
|
13515
|
+
* Directory under `.claude/` where Claude Code keeps the memory of a subagent
|
|
13516
|
+
* declared with `memory: local`; documented as not checked into version control.
|
|
13517
|
+
* @see https://code.claude.com/docs/en/sub-agents#enable-persistent-memory
|
|
13518
|
+
*/
|
|
13519
|
+
const CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME = "agent-memory-local";
|
|
13378
13520
|
const CLAUDECODE_COMMANDS_DIR_PATH = (0, node_path.join)(CLAUDECODE_DIR, "commands");
|
|
13379
13521
|
const CLAUDECODE_AGENTS_DIR_PATH = (0, node_path.join)(CLAUDECODE_DIR, "agents");
|
|
13380
13522
|
const CLAUDECODE_SKILLS_DIR_PATH = (0, node_path.join)(CLAUDECODE_DIR, "skills");
|
|
@@ -14555,6 +14697,22 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
14555
14697
|
}
|
|
14556
14698
|
}
|
|
14557
14699
|
},
|
|
14700
|
+
".omp/config.yml": {
|
|
14701
|
+
format: "yaml",
|
|
14702
|
+
invalidRootPolicy: "error",
|
|
14703
|
+
features: { permissions: {
|
|
14704
|
+
kind: "replace-owned-keys",
|
|
14705
|
+
ownedKeys: ["tools", "bash"]
|
|
14706
|
+
} }
|
|
14707
|
+
},
|
|
14708
|
+
".omp/agent/config.yml": {
|
|
14709
|
+
format: "yaml",
|
|
14710
|
+
invalidRootPolicy: "error",
|
|
14711
|
+
features: { permissions: {
|
|
14712
|
+
kind: "replace-owned-keys",
|
|
14713
|
+
ownedKeys: ["tools", "bash"]
|
|
14714
|
+
} }
|
|
14715
|
+
},
|
|
14558
14716
|
".pi/settings.json": {
|
|
14559
14717
|
format: "json",
|
|
14560
14718
|
invalidRootPolicy: "error",
|
|
@@ -15601,6 +15759,14 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
15601
15759
|
filePattern: "*.json"
|
|
15602
15760
|
}
|
|
15603
15761
|
}],
|
|
15762
|
+
["kilo", {
|
|
15763
|
+
class: KiloCheck,
|
|
15764
|
+
meta: {
|
|
15765
|
+
supportsGlobal: false,
|
|
15766
|
+
filePattern: KILO_REVIEW_FILE_NAME,
|
|
15767
|
+
committedOutput: true
|
|
15768
|
+
}
|
|
15769
|
+
}],
|
|
15604
15770
|
["qwencode", {
|
|
15605
15771
|
class: QwencodeCheck,
|
|
15606
15772
|
meta: {
|
|
@@ -16382,6 +16548,7 @@ const ANTIGRAVITY_IDE_RULE_FILE_NAME = "AGENTS.md";
|
|
|
16382
16548
|
const ANTIGRAVITY_IDE_GEMINI_DIR = ".gemini";
|
|
16383
16549
|
const ANTIGRAVITY_IDE_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
|
|
16384
16550
|
const ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR = "config";
|
|
16551
|
+
const ANTIGRAVITY_IDE_GLOBAL_RULES_SUBDIR = (0, node_path.join)(ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR, "rules");
|
|
16385
16552
|
const ANTIGRAVITY_IDE_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_IDE_GEMINI_DIR, "antigravity", "global_workflows");
|
|
16386
16553
|
const ANTIGRAVITY_IDE_PERMISSIONS_DIR = ".antigravity";
|
|
16387
16554
|
const ANTIGRAVITY_IDE_PERMISSIONS_FILE_NAME = "settings.json";
|
|
@@ -16660,6 +16827,7 @@ const BOB_COMMANDS_DIR_PATH = (0, node_path.join)(BOB_DIR, "commands");
|
|
|
16660
16827
|
const BOB_SKILLS_DIR_PATH = (0, node_path.join)(BOB_DIR, "skills");
|
|
16661
16828
|
const BOB_SETTINGS_FILE_NAME = "settings.json";
|
|
16662
16829
|
const BOB_GLOBAL_SETTINGS_DIR_PATH = (0, node_path.join)(BOB_DIR, "settings");
|
|
16830
|
+
const BOB_CUSTOM_MODES_FILE_NAME = "custom_modes.yaml";
|
|
16663
16831
|
//#endregion
|
|
16664
16832
|
//#region src/features/commands/bob-command.ts
|
|
16665
16833
|
/**
|
|
@@ -17189,6 +17357,7 @@ const CODEWHALE_COMMANDS_DIR_PATH = (0, node_path.join)(CODEWHALE_DIR, "commands
|
|
|
17189
17357
|
const CODEWHALE_HOOKS_FILE_NAME = "hooks.toml";
|
|
17190
17358
|
const CODEWHALE_CONFIG_FILE_NAME = "config.toml";
|
|
17191
17359
|
const CODEWHALE_MCP_FILE_NAME = "mcp.json";
|
|
17360
|
+
const CODEWHALE_PERMISSIONS_FILE_NAME = "permissions.toml";
|
|
17192
17361
|
//#endregion
|
|
17193
17362
|
//#region src/features/commands/codewhale-command.ts
|
|
17194
17363
|
/**
|
|
@@ -20121,25 +20290,6 @@ var JunieCommand = class JunieCommand extends ToolCommand {
|
|
|
20121
20290
|
}
|
|
20122
20291
|
};
|
|
20123
20292
|
//#endregion
|
|
20124
|
-
//#region src/constants/kilo-paths.ts
|
|
20125
|
-
const KILO_DIR = ".kilo";
|
|
20126
|
-
const KILO_GLOBAL_DIR = (0, node_path.join)(".config", "kilo");
|
|
20127
|
-
const KILO_RULES_DIR_NAME = "rules";
|
|
20128
|
-
const KILO_COMMANDS_DIR_PATH = (0, node_path.join)(KILO_DIR, "commands");
|
|
20129
|
-
const KILO_GLOBAL_COMMANDS_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "commands");
|
|
20130
|
-
const KILO_SKILLS_DIR_PATH = (0, node_path.join)(KILO_DIR, "skills");
|
|
20131
|
-
const KILO_AGENTS_DIR_PATH = (0, node_path.join)(KILO_DIR, "agents");
|
|
20132
|
-
const KILO_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "agents");
|
|
20133
|
-
const KILO_PLUGINS_DIR_PATH = (0, node_path.join)(KILO_DIR, "plugins");
|
|
20134
|
-
const KILO_GLOBAL_PLUGINS_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "plugins");
|
|
20135
|
-
const KILO_PLUGIN_DIR_PATH = (0, node_path.join)(KILO_DIR, "plugin");
|
|
20136
|
-
const KILO_GLOBAL_PLUGIN_DIR_PATH = (0, node_path.join)(KILO_GLOBAL_DIR, "plugin");
|
|
20137
|
-
const KILO_RULE_FILE_NAME = "AGENTS.md";
|
|
20138
|
-
const KILO_IGNORE_FILE_NAME = ".kilocodeignore";
|
|
20139
|
-
const KILO_JSONC_FILE_NAME = "kilo.jsonc";
|
|
20140
|
-
const KILO_JSON_FILE_NAME = "kilo.json";
|
|
20141
|
-
const KILO_HOOKS_FILE_NAME = "rulesync-hooks.js";
|
|
20142
|
-
//#endregion
|
|
20143
20293
|
//#region src/features/commands/kilo-command.ts
|
|
20144
20294
|
const KiloCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
20145
20295
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -20633,9 +20783,26 @@ const OpenCodeCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
20633
20783
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
20634
20784
|
agent: (0, zod_mini.optional)(zod_mini.z.string()),
|
|
20635
20785
|
subtask: (0, zod_mini.optional)(zod_mini.z.boolean()),
|
|
20786
|
+
subagent: (0, zod_mini.optional)(zod_mini.z.boolean()),
|
|
20636
20787
|
model: (0, zod_mini.optional)(zod_mini.z.string()),
|
|
20637
20788
|
variant: (0, zod_mini.optional)(zod_mini.z.string())
|
|
20638
20789
|
});
|
|
20790
|
+
/**
|
|
20791
|
+
* Rewrites a boolean `subagent` (OpenCode V2) into `subtask`, the key both V1
|
|
20792
|
+
* and V2 read: V1's command schema rejects unknown keys, while V2 resolves
|
|
20793
|
+
* `subagent ?? subtask`. `subagent` therefore wins when both are set, matching
|
|
20794
|
+
* V2. A non-boolean `subagent` is left untouched for validation to report.
|
|
20795
|
+
*
|
|
20796
|
+
* @see https://opencode.ai/v2/docs/commands/
|
|
20797
|
+
*/
|
|
20798
|
+
function foldSubagentAlias(fields) {
|
|
20799
|
+
if (typeof fields.subagent !== "boolean") return fields;
|
|
20800
|
+
const { subagent, ...rest } = fields;
|
|
20801
|
+
return {
|
|
20802
|
+
...rest,
|
|
20803
|
+
subtask: subagent
|
|
20804
|
+
};
|
|
20805
|
+
}
|
|
20639
20806
|
var OpenCodeCommand = class extends ToolCommand {
|
|
20640
20807
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
20641
20808
|
static layout = OPENCODE_LAYOUT;
|
|
@@ -20663,7 +20830,8 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
20663
20830
|
return this.frontmatter;
|
|
20664
20831
|
}
|
|
20665
20832
|
toRulesyncCommand() {
|
|
20666
|
-
const { description, ...
|
|
20833
|
+
const { description, ...toolFields } = this.frontmatter;
|
|
20834
|
+
const restFields = foldSubagentAlias(toolFields);
|
|
20667
20835
|
const { toolTarget } = this.constructor.layout;
|
|
20668
20836
|
const rulesyncFrontmatter = {
|
|
20669
20837
|
targets: ["*"],
|
|
@@ -20683,7 +20851,7 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
20683
20851
|
}
|
|
20684
20852
|
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
20685
20853
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
20686
|
-
const opencodeFields = rulesyncFrontmatter[this.layout.toolTarget] ?? {};
|
|
20854
|
+
const opencodeFields = foldSubagentAlias(rulesyncFrontmatter[this.layout.toolTarget] ?? {});
|
|
20687
20855
|
const opencodeFrontmatter = {
|
|
20688
20856
|
description: rulesyncFrontmatter.description,
|
|
20689
20857
|
...opencodeFields
|
|
@@ -20739,7 +20907,8 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
20739
20907
|
* Imports commands defined inline in `opencode.json` / `opencode.jsonc` under
|
|
20740
20908
|
* the top-level `command` key (in addition to the Markdown files under
|
|
20741
20909
|
* `.opencode/commands/`). Each entry's `template` becomes the command body,
|
|
20742
|
-
* while `description` / `agent` / `model` / `subtask` map to the frontmatter
|
|
20910
|
+
* while `description` / `agent` / `model` / `subtask` map to the frontmatter
|
|
20911
|
+
* (a V2 `subagent` is read as `subtask`, taking precedence like V2 does).
|
|
20743
20912
|
*
|
|
20744
20913
|
* Import-only: this is invoked by the commands processor when loading tool
|
|
20745
20914
|
* files for conversion to rulesync, never for orphan deletion.
|
|
@@ -20759,11 +20928,12 @@ var OpenCodeCommand = class extends ToolCommand {
|
|
|
20759
20928
|
const entry = asOpencodeEntries(rawEntry);
|
|
20760
20929
|
if (!entry) continue;
|
|
20761
20930
|
const body = typeof entry.template === "string" ? entry.template : "";
|
|
20931
|
+
const { subtask } = foldSubagentAlias(entry);
|
|
20762
20932
|
const frontmatter = {
|
|
20763
20933
|
...typeof entry.description === "string" && { description: entry.description },
|
|
20764
20934
|
...typeof entry.agent === "string" && { agent: entry.agent },
|
|
20765
20935
|
...typeof entry.model === "string" && { model: entry.model },
|
|
20766
|
-
...typeof
|
|
20936
|
+
...typeof subtask === "boolean" && { subtask }
|
|
20767
20937
|
};
|
|
20768
20938
|
commands.push(new this({
|
|
20769
20939
|
outputRoot,
|
|
@@ -20816,6 +20986,7 @@ const OMP_RULE_FILE_NAME = "AGENTS.md";
|
|
|
20816
20986
|
const OMP_RULES_DIR_NAME = "rules";
|
|
20817
20987
|
const OMP_AGENTS_DIR_NAME = "agents";
|
|
20818
20988
|
const OMP_MCP_FILE_NAME = "mcp.json";
|
|
20989
|
+
const OMP_CONFIG_FILE_NAME = "config.yml";
|
|
20819
20990
|
const OMP_LAYOUT = {
|
|
20820
20991
|
toolTarget: "omp",
|
|
20821
20992
|
dir: OMP_DIR,
|
|
@@ -22145,6 +22316,7 @@ function warpcliConfigDir() {
|
|
|
22145
22316
|
}
|
|
22146
22317
|
}
|
|
22147
22318
|
const WARP_RULE_FILE_NAME = "AGENTS.md";
|
|
22319
|
+
const WARP_LEGACY_RULE_FILE_NAME = "WARP.md";
|
|
22148
22320
|
const WARP_GLOBAL_RULE_DIR = ".agents";
|
|
22149
22321
|
const WARP_MCP_FILE_NAME = ".mcp.json";
|
|
22150
22322
|
const WARP_PERMISSIONS_FILE_NAME = "settings.toml";
|
|
@@ -35725,7 +35897,9 @@ var JunieIgnore = class JunieIgnore extends ToolIgnore {
|
|
|
35725
35897
|
* KiloIgnore represents ignore patterns for the Kilo Code VSCode extension.
|
|
35726
35898
|
*
|
|
35727
35899
|
* Based on the Kilo Code specification:
|
|
35728
|
-
* - File location:
|
|
35900
|
+
* - File location: workspace root (.kilocodeignore) for project scope, and
|
|
35901
|
+
* `~/.kilocode/.kilocodeignore` for global scope. Kilo merges the global
|
|
35902
|
+
* patterns first and the project ones after, so the project file wins.
|
|
35729
35903
|
* - Syntax: Same as .gitignore
|
|
35730
35904
|
* - Immediate reflection when saved
|
|
35731
35905
|
* - Complete blocking of file access for ignored patterns
|
|
@@ -35733,11 +35907,12 @@ var JunieIgnore = class JunieIgnore extends ToolIgnore {
|
|
|
35733
35907
|
*
|
|
35734
35908
|
* Kilo reads `.kilocodeignore` (not `.kiloignore`), so emitting `.kiloignore`
|
|
35735
35909
|
* left the file inert. https://kilo.ai/docs/customize/context/kilocodeignore
|
|
35910
|
+
* @see https://github.com/Kilo-Org/kilocode/blob/main/packages/opencode/src/kilocode/ignore-migrator.ts
|
|
35736
35911
|
*/
|
|
35737
35912
|
var KiloIgnore = class KiloIgnore extends ToolIgnore {
|
|
35738
|
-
static getSettablePaths() {
|
|
35913
|
+
static getSettablePaths({ global = false } = {}) {
|
|
35739
35914
|
return {
|
|
35740
|
-
relativeDirPath: ".",
|
|
35915
|
+
relativeDirPath: global ? KILO_LEGACY_GLOBAL_DIR : ".",
|
|
35741
35916
|
relativeFilePath: KILO_IGNORE_FILE_NAME
|
|
35742
35917
|
};
|
|
35743
35918
|
}
|
|
@@ -35750,35 +35925,40 @@ var KiloIgnore = class KiloIgnore extends ToolIgnore {
|
|
|
35750
35925
|
/**
|
|
35751
35926
|
* Create KiloIgnore from RulesyncIgnore
|
|
35752
35927
|
*/
|
|
35753
|
-
static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
|
|
35928
|
+
static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
35754
35929
|
const body = rulesyncIgnore.getFileContent();
|
|
35930
|
+
const paths = this.getSettablePaths({ global });
|
|
35755
35931
|
return new KiloIgnore({
|
|
35756
35932
|
outputRoot,
|
|
35757
|
-
relativeDirPath:
|
|
35758
|
-
relativeFilePath:
|
|
35759
|
-
fileContent: body
|
|
35933
|
+
relativeDirPath: paths.relativeDirPath,
|
|
35934
|
+
relativeFilePath: paths.relativeFilePath,
|
|
35935
|
+
fileContent: body,
|
|
35936
|
+
global
|
|
35760
35937
|
});
|
|
35761
35938
|
}
|
|
35762
35939
|
/**
|
|
35763
35940
|
* Load KiloIgnore from .kilocodeignore file
|
|
35764
35941
|
*/
|
|
35765
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
35766
|
-
const
|
|
35942
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
35943
|
+
const paths = this.getSettablePaths({ global });
|
|
35944
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
35767
35945
|
return new KiloIgnore({
|
|
35768
35946
|
outputRoot,
|
|
35769
|
-
relativeDirPath:
|
|
35770
|
-
relativeFilePath:
|
|
35947
|
+
relativeDirPath: paths.relativeDirPath,
|
|
35948
|
+
relativeFilePath: paths.relativeFilePath,
|
|
35771
35949
|
fileContent,
|
|
35772
|
-
validate
|
|
35950
|
+
validate,
|
|
35951
|
+
global
|
|
35773
35952
|
});
|
|
35774
35953
|
}
|
|
35775
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
35954
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
35776
35955
|
return new KiloIgnore({
|
|
35777
35956
|
outputRoot,
|
|
35778
35957
|
relativeDirPath,
|
|
35779
35958
|
relativeFilePath,
|
|
35780
35959
|
fileContent: "",
|
|
35781
|
-
validate: false
|
|
35960
|
+
validate: false,
|
|
35961
|
+
global
|
|
35782
35962
|
});
|
|
35783
35963
|
}
|
|
35784
35964
|
};
|
|
@@ -36452,6 +36632,7 @@ const ignoreProcessorGlobalToolTargets = [
|
|
|
36452
36632
|
"continue",
|
|
36453
36633
|
"devin",
|
|
36454
36634
|
"grokcli",
|
|
36635
|
+
"kilo",
|
|
36455
36636
|
"kiro",
|
|
36456
36637
|
"kiro-cli",
|
|
36457
36638
|
"kiro-ide",
|
|
@@ -40313,6 +40494,11 @@ var CrushMcp = class CrushMcp extends ToolMcp {
|
|
|
40313
40494
|
* `g` flag is required because callers may have multiple references per value.
|
|
40314
40495
|
*/
|
|
40315
40496
|
const CANONICAL_ENV_VAR_PATTERN = /\$\{(?!env:)([^}:]+)\}/g;
|
|
40497
|
+
/**
|
|
40498
|
+
* OpenCode / Kilo `{env:VAR}` env var reference pattern. The negative lookbehind
|
|
40499
|
+
* avoids matching Cursor's `${env:VAR}` format.
|
|
40500
|
+
*/
|
|
40501
|
+
const BRACE_ENV_VAR_PATTERN = /(?<!\$)\{env:([^}:]+)\}/g;
|
|
40316
40502
|
function convertRecordValues({ record, pattern, replacement }) {
|
|
40317
40503
|
return Object.fromEntries(Object.entries(record).map(([k, v]) => [k, v.replace(pattern, replacement)]));
|
|
40318
40504
|
}
|
|
@@ -40364,6 +40550,14 @@ function convertEnvVarRefsToToolFormat({ mcpServers, replacement }) {
|
|
|
40364
40550
|
}) }
|
|
40365
40551
|
}]));
|
|
40366
40552
|
}
|
|
40553
|
+
/**
|
|
40554
|
+
* Names of the servers whose `env` or `headers` values carry at least one
|
|
40555
|
+
* canonical `${VAR}` env var reference.
|
|
40556
|
+
*/
|
|
40557
|
+
function findServersWithEnvVarRefs(mcpServers) {
|
|
40558
|
+
const hasRef = (record) => Object.values(record ?? {}).some((value) => value.search(CANONICAL_ENV_VAR_PATTERN) !== -1);
|
|
40559
|
+
return Object.entries(mcpServers).filter(([, config]) => hasRef(config.env) || hasRef(config.headers)).map(([name]) => name);
|
|
40560
|
+
}
|
|
40367
40561
|
//#endregion
|
|
40368
40562
|
//#region src/features/mcp/cursor-mcp.ts
|
|
40369
40563
|
const CURSOR_ENV_VAR_PATTERN = /\$\{env:([^}:]+)\}/g;
|
|
@@ -42652,17 +42846,50 @@ function readExistingKiloMcpEntries(fileContent) {
|
|
|
42652
42846
|
}
|
|
42653
42847
|
return entries;
|
|
42654
42848
|
}
|
|
42655
|
-
|
|
42849
|
+
const KILO_UNTRUSTED_ENV_REF_PATTERN = /\{env:[^}]+\}/;
|
|
42850
|
+
/**
|
|
42851
|
+
* Judged on the entry as written, not the canonical server: fields the entry
|
|
42852
|
+
* drops (a toggle keeps only `enabled`) cannot make Kilo reject the file.
|
|
42853
|
+
*/
|
|
42854
|
+
function rejectUntrustedEnvRef(serverName, converted, rejectEnvRefs, logger) {
|
|
42855
|
+
if (!rejectEnvRefs || converted === null) return converted;
|
|
42856
|
+
if (!KILO_UNTRUSTED_ENV_REF_PATTERN.test(JSON.stringify(converted))) return converted;
|
|
42857
|
+
return warnAndSkipMcpServer({
|
|
42858
|
+
toolName: "Kilo",
|
|
42859
|
+
serverName,
|
|
42860
|
+
reason: "an {env:...} reference, which makes Kilo reject the whole project config",
|
|
42861
|
+
logger
|
|
42862
|
+
});
|
|
42863
|
+
}
|
|
42864
|
+
function convertToKiloFormat(mcpServers, existingMcp, logger, rejectEnvRefs = false) {
|
|
42656
42865
|
const tools = {};
|
|
42657
42866
|
return {
|
|
42658
42867
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
42659
|
-
const converted = convertServerToKiloFormat(serverName, serverConfig, Object.hasOwn(existingMcp, serverName) ? existingMcp[serverName] : void 0, logger);
|
|
42868
|
+
const converted = rejectUntrustedEnvRef(serverName, convertServerToKiloFormat(serverName, serverConfig, Object.hasOwn(existingMcp, serverName) ? existingMcp[serverName] : void 0, logger), rejectEnvRefs, logger);
|
|
42660
42869
|
collectKiloServerTools(tools, serverName, serverConfig);
|
|
42661
42870
|
return converted === null ? null : [serverName, converted];
|
|
42662
42871
|
}).filter((entry) => entry !== null)),
|
|
42663
42872
|
tools
|
|
42664
42873
|
};
|
|
42665
42874
|
}
|
|
42875
|
+
/**
|
|
42876
|
+
* Kilo expands only `{env:VAR}`, never the canonical `${VAR}`, and only in a
|
|
42877
|
+
* trusted config: the global `~/.config/kilo/kilo.jsonc`. A project config is
|
|
42878
|
+
* untrusted, so any `{env:` in it makes Kilo drop the whole file (or, in MCP
|
|
42879
|
+
* headers, the server). In project scope canonical references are therefore
|
|
42880
|
+
* left as written — Kilo passes them through literally — with a warning (a
|
|
42881
|
+
* written entry already carrying `{env:` is skipped by `convertToKiloFormat`).
|
|
42882
|
+
* @see https://github.com/Kilo-Org/kilocode/blob/main/packages/opencode/src/config/variable.ts
|
|
42883
|
+
*/
|
|
42884
|
+
function resolveKiloEnvVarRefs({ mcpServers, global, logger }) {
|
|
42885
|
+
if (global) return convertEnvVarRefsToToolFormat({
|
|
42886
|
+
mcpServers,
|
|
42887
|
+
replacement: "{env:$1}"
|
|
42888
|
+
});
|
|
42889
|
+
const serverNames = findServersWithEnvVarRefs(mcpServers);
|
|
42890
|
+
if (serverNames.length > 0) logger?.warn(`Kilo MCP servers ${serverNames.map((name) => `"${name}"`).join(", ")} use environment variable references (\${VAR}), which Kilo does not resolve in a project config; they are written literally. Use global mode (~/.config/kilo/kilo.jsonc) to have them expanded.`);
|
|
42891
|
+
return mcpServers;
|
|
42892
|
+
}
|
|
42666
42893
|
var KiloMcp = class KiloMcp extends ToolMcp {
|
|
42667
42894
|
json;
|
|
42668
42895
|
constructor(params) {
|
|
@@ -42761,7 +42988,11 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
42761
42988
|
if (fileContent) relativeFilePath = KILO_JSON_FILE_NAME;
|
|
42762
42989
|
}
|
|
42763
42990
|
const existingMcp = readExistingKiloMcpEntries(fileContent);
|
|
42764
|
-
const { mcp: convertedMcp, tools: mcpTools } = convertToKiloFormat(
|
|
42991
|
+
const { mcp: convertedMcp, tools: mcpTools } = convertToKiloFormat(resolveKiloEnvVarRefs({
|
|
42992
|
+
mcpServers: rulesyncMcp.getMcpServers(),
|
|
42993
|
+
global,
|
|
42994
|
+
logger
|
|
42995
|
+
}), existingMcp, logger, !global);
|
|
42765
42996
|
return new KiloMcp({
|
|
42766
42997
|
outputRoot,
|
|
42767
42998
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -42827,7 +43058,10 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
42827
43058
|
});
|
|
42828
43059
|
}
|
|
42829
43060
|
toRulesyncMcp() {
|
|
42830
|
-
const { shared, toolOnly } = splitMcpServersByTransport(
|
|
43061
|
+
const { shared, toolOnly } = splitMcpServersByTransport(convertEnvVarRefsFromToolFormat({
|
|
43062
|
+
mcpServers: convertFromKiloFormat(this.json.mcp ?? {}, this.json.tools),
|
|
43063
|
+
pattern: BRACE_ENV_VAR_PATTERN
|
|
43064
|
+
}));
|
|
42831
43065
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
42832
43066
|
mcpServers: shared,
|
|
42833
43067
|
...Object.keys(toolOnly).length > 0 && { kilo: { mcpServers: toolOnly } }
|
|
@@ -43284,7 +43518,6 @@ var KiroMcp = class KiroMcp extends ToolMcp {
|
|
|
43284
43518
|
};
|
|
43285
43519
|
//#endregion
|
|
43286
43520
|
//#region src/features/mcp/opencode-mcp.ts
|
|
43287
|
-
const OPENCODE_ENV_VAR_PATTERN = /(?<!\$)\{env:([^}:]+)\}/g;
|
|
43288
43521
|
const OpencodeMcpLocalServerSchema = zod_mini.z.looseObject({
|
|
43289
43522
|
type: zod_mini.z.literal("local"),
|
|
43290
43523
|
command: zod_mini.z.array(zod_mini.z.string()),
|
|
@@ -43667,7 +43900,7 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43667
43900
|
toRulesyncMcp() {
|
|
43668
43901
|
const { shared, toolOnly } = splitMcpServersByTransport(convertEnvVarRefsFromToolFormat({
|
|
43669
43902
|
mcpServers: convertFromOpencodeFormat(this.json.mcp ?? {}, this.json.tools),
|
|
43670
|
-
pattern:
|
|
43903
|
+
pattern: BRACE_ENV_VAR_PATTERN
|
|
43671
43904
|
}));
|
|
43672
43905
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
|
|
43673
43906
|
mcpServers: shared,
|
|
@@ -44950,6 +45183,28 @@ const REASONIX_RETIRED_PLUGIN_FIELDS = ["trusted_read_only_tools"];
|
|
|
44950
45183
|
function warnAboutRetiredFields({ name, serverRecord, logger }) {
|
|
44951
45184
|
for (const field of REASONIX_RETIRED_PLUGIN_FIELDS) if (serverRecord[field] !== void 0) logger?.warn(`Reasonix MCP: dropping "${field}" from "${name}"; Reasonix retired the field in v1.17.18 and ignores it, so it is no longer written.`);
|
|
44952
45185
|
}
|
|
45186
|
+
/**
|
|
45187
|
+
* The `concurrency` values Reasonix honors. It trims and lowercases before
|
|
45188
|
+
* comparing (`mcpServerIsSerial` in `internal/agent/mcp_concurrency.go`), so
|
|
45189
|
+
* `" Serial "` counts too and is written as authored.
|
|
45190
|
+
*/
|
|
45191
|
+
const REASONIX_CONCURRENCY_VALUES = /* @__PURE__ */ new Set(["serial", "parallel"]);
|
|
45192
|
+
/**
|
|
45193
|
+
* Why a `concurrency` / `auto_start` value cannot be written, or `undefined`
|
|
45194
|
+
* when it can (and for every other field). A wrong type is the case that
|
|
45195
|
+
* matters: Reasonix declares the fields as `string` / `*bool`, and a TOML type
|
|
45196
|
+
* mismatch makes it refuse the whole config file. An unknown `concurrency`
|
|
45197
|
+
* string decodes fine but is ignored in favor of the server-name default, so it
|
|
45198
|
+
* is dropped too rather than written as a setting that does nothing.
|
|
45199
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/internal/config/plugin_entry.go
|
|
45200
|
+
*/
|
|
45201
|
+
function invalidSchedulingFieldReason(field, value) {
|
|
45202
|
+
if (field === "auto_start" && typeof value !== "boolean") return "Reasonix expects a boolean and fails to load a config file holding any other type.";
|
|
45203
|
+
if (field === "concurrency") {
|
|
45204
|
+
if (typeof value !== "string") return "Reasonix expects a string and fails to load a config file holding any other type.";
|
|
45205
|
+
if (!REASONIX_CONCURRENCY_VALUES.has(value.trim().toLowerCase())) return `Reasonix accepts only "serial" or "parallel" and ignores ${JSON.stringify(value)}, falling back to the server-name default.`;
|
|
45206
|
+
}
|
|
45207
|
+
}
|
|
44953
45208
|
function rulesyncMcpServerToReasonix(name, server, logger) {
|
|
44954
45209
|
const serverRecord = server;
|
|
44955
45210
|
const type = resolveReasonixType(server);
|
|
@@ -44974,7 +45229,14 @@ function rulesyncMcpServerToReasonix(name, server, logger) {
|
|
|
44974
45229
|
}
|
|
44975
45230
|
for (const field of REASONIX_PLUGIN_FIELDS) {
|
|
44976
45231
|
if (field === "type" || field === "command" || field === "args") continue;
|
|
44977
|
-
|
|
45232
|
+
const value = serverRecord[field];
|
|
45233
|
+
if (value === void 0) continue;
|
|
45234
|
+
const invalidReason = invalidSchedulingFieldReason(field, value);
|
|
45235
|
+
if (invalidReason !== void 0) {
|
|
45236
|
+
logger?.warn(`Reasonix MCP: dropping "${field}" from "${name}"; ${invalidReason}`);
|
|
45237
|
+
continue;
|
|
45238
|
+
}
|
|
45239
|
+
plugin[field] = value;
|
|
44978
45240
|
}
|
|
44979
45241
|
warnAboutRetiredFields({
|
|
44980
45242
|
name,
|
|
@@ -52041,7 +52303,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
52041
52303
|
"ask",
|
|
52042
52304
|
"deny"
|
|
52043
52305
|
];
|
|
52044
|
-
const CATCH_ALL_PATTERN$
|
|
52306
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
52045
52307
|
const MCP_CATEGORY = "mcp";
|
|
52046
52308
|
const MCP_RULE_PREFIX = "mcp__";
|
|
52047
52309
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -52114,7 +52376,7 @@ function categoryOfRule$2(rule) {
|
|
|
52114
52376
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
52115
52377
|
*/
|
|
52116
52378
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
52117
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
52379
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$11;
|
|
52118
52380
|
if (category === MCP_CATEGORY) {
|
|
52119
52381
|
if (catchAll && action === "allow") {
|
|
52120
52382
|
logger?.warn(`CodeBuddy permissions: skipping the 'mcp' allow rule '*' — CodeBuddy honors the all-MCP wildcard 'mcp__*' in deny and ask only. Allow servers by name instead (for example 'mcp__github').`);
|
|
@@ -52123,7 +52385,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
52123
52385
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
52124
52386
|
}
|
|
52125
52387
|
if (category === "*") {
|
|
52126
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
52388
|
+
if (catchAll) return CATCH_ALL_PATTERN$11;
|
|
52127
52389
|
logger?.warn(`CodeBuddy permissions: skipping the all-tools '*' rule '${pattern}' — CodeBuddy's standalone '*' rule takes no specifier. Write it under the category it is meant for (for example 'bash' or 'read').`);
|
|
52128
52390
|
return null;
|
|
52129
52391
|
}
|
|
@@ -52141,7 +52403,7 @@ function parseCodebuddyRule(rule) {
|
|
|
52141
52403
|
const { specifier } = splitCodebuddyRule(rule);
|
|
52142
52404
|
if (specifier === void 0) return {
|
|
52143
52405
|
category,
|
|
52144
|
-
pattern: CATCH_ALL_PATTERN$
|
|
52406
|
+
pattern: CATCH_ALL_PATTERN$11
|
|
52145
52407
|
};
|
|
52146
52408
|
const trimmed = specifier.trim();
|
|
52147
52409
|
return trimmed === "" ? null : {
|
|
@@ -52317,6 +52579,633 @@ function parseCodebuddySettings({ fileContent, relativePath }) {
|
|
|
52317
52579
|
}
|
|
52318
52580
|
}
|
|
52319
52581
|
//#endregion
|
|
52582
|
+
//#region src/features/permissions/codewhale-permissions.ts
|
|
52583
|
+
const CODEWHALE_SHELL_TOOL = "exec_shell";
|
|
52584
|
+
/**
|
|
52585
|
+
* Canonical path categories and the Codewhale policy tool names their rules are
|
|
52586
|
+
* written for. Codewhale consults typed rules only for `exec_shell` and these
|
|
52587
|
+
* file tools, so a rule for any other tool (`fetch_url`, `web_search`, an MCP
|
|
52588
|
+
* tool, ...) is never evaluated. `edit` covers `apply_patch` too: both modify
|
|
52589
|
+
* existing files, and an `edit` deny that left `apply_patch` open would not
|
|
52590
|
+
* hold.
|
|
52591
|
+
*
|
|
52592
|
+
* @see https://github.com/Hmbown/Codewhale/blob/main/crates/tui/src/core/engine.rs (`file_tool_permission_paths`)
|
|
52593
|
+
* @see https://github.com/Hmbown/Codewhale/blob/main/crates/tui/src/tools/canonical_action.rs
|
|
52594
|
+
*/
|
|
52595
|
+
const CODEWHALE_PATH_CATEGORY_TOOLS = {
|
|
52596
|
+
read: ["read_file"],
|
|
52597
|
+
write: ["write_file"],
|
|
52598
|
+
edit: ["edit_file", "apply_patch"],
|
|
52599
|
+
grep: ["grep_files"],
|
|
52600
|
+
glob: ["file_search"],
|
|
52601
|
+
list: ["list_dir"]
|
|
52602
|
+
};
|
|
52603
|
+
/**
|
|
52604
|
+
* `apply_patch` can also create files (a `--- /dev/null` hunk), so a `write`
|
|
52605
|
+
* restriction is written for it too; a `write` allow is not, since it would
|
|
52606
|
+
* approve edits to existing files the `edit` category never allowed.
|
|
52607
|
+
*
|
|
52608
|
+
* @see https://github.com/Hmbown/Codewhale/blob/main/crates/tui/src/tools/apply_patch.rs
|
|
52609
|
+
*/
|
|
52610
|
+
const CODEWHALE_WRITE_RESTRICTION_TOOLS = ["write_file", "apply_patch"];
|
|
52611
|
+
/** The Codewhale tools a canonical path rule is written for. */
|
|
52612
|
+
function pathCategoryTools(category, action) {
|
|
52613
|
+
if (category === "write" && action !== "allow") return CODEWHALE_WRITE_RESTRICTION_TOOLS;
|
|
52614
|
+
return CODEWHALE_PATH_CATEGORY_TOOLS[category] ?? [];
|
|
52615
|
+
}
|
|
52616
|
+
/** The canonical path categories a Codewhale file tool's rule can come from. */
|
|
52617
|
+
const CODEWHALE_TOOL_TO_PATH_CATEGORIES = {
|
|
52618
|
+
read_file: ["read"],
|
|
52619
|
+
write_file: ["write"],
|
|
52620
|
+
edit_file: ["edit"],
|
|
52621
|
+
apply_patch: ["edit", "write"],
|
|
52622
|
+
grep_files: ["grep"],
|
|
52623
|
+
file_search: ["glob"],
|
|
52624
|
+
list_dir: ["list"]
|
|
52625
|
+
};
|
|
52626
|
+
const CODEWHALE_GLOBAL_ONLY_MESSAGE = "Codewhale permissions are global-only; use --global to sync ~/.codewhale/permissions.toml";
|
|
52627
|
+
const COMMAND_WILDCARD_CHARACTERS = /[*?[]/;
|
|
52628
|
+
const PATH_GLOB_CHARACTERS = /[*?[\]{}]/;
|
|
52629
|
+
function isCatchAllPattern(pattern) {
|
|
52630
|
+
return pattern === "*" || pattern === "**";
|
|
52631
|
+
}
|
|
52632
|
+
/**
|
|
52633
|
+
* The literal command prefix a wildcard pattern pins down, cut back to a word
|
|
52634
|
+
* boundary so it never names a partial word (`git push* --force` pins down
|
|
52635
|
+
* `git`, not `git push`). Empty when the pattern starts with a wildcard.
|
|
52636
|
+
*/
|
|
52637
|
+
function literalCommandPrefix(pattern) {
|
|
52638
|
+
const firstWildcard = pattern.search(COMMAND_WILDCARD_CHARACTERS);
|
|
52639
|
+
const head = pattern.slice(0, firstWildcard);
|
|
52640
|
+
if (/\s$/.test(head)) return head.trim();
|
|
52641
|
+
const lastBoundary = head.search(/\s\S*$/);
|
|
52642
|
+
return lastBoundary < 0 ? "" : head.slice(0, lastBoundary).trim();
|
|
52643
|
+
}
|
|
52644
|
+
/**
|
|
52645
|
+
* Convert one canonical `bash` rule. Codewhale matches `command` as a
|
|
52646
|
+
* word-boundary prefix of the invocation (or the whole invocation with
|
|
52647
|
+
* `command_exact = true`); it expands no wildcards.
|
|
52648
|
+
*
|
|
52649
|
+
* - `*` / `**` → a tool-wide rule.
|
|
52650
|
+
* - `git *` → the prefix `git`.
|
|
52651
|
+
* - A wildcard-free `allow` → an exact command, so it approves nothing more
|
|
52652
|
+
* than the command it names. A wildcard-free `deny` / `ask` → a prefix, the
|
|
52653
|
+
* way Codewhale's own deny rules are matched, so the restriction also covers
|
|
52654
|
+
* the command with more arguments.
|
|
52655
|
+
* - Any other wildcard: a restricting rule is broadened to the literal prefix
|
|
52656
|
+
* it pins down; an `allow` is skipped, since no prefix approves only what it
|
|
52657
|
+
* names.
|
|
52658
|
+
*/
|
|
52659
|
+
function convertShellRule({ pattern, action, source = SHELL_PERMISSION_CATEGORY, logger }) {
|
|
52660
|
+
const trimmed = pattern.trim();
|
|
52661
|
+
if (isCatchAllPattern(trimmed)) return {
|
|
52662
|
+
tool: CODEWHALE_SHELL_TOOL,
|
|
52663
|
+
action
|
|
52664
|
+
};
|
|
52665
|
+
if (trimmed === "") {
|
|
52666
|
+
logger?.warn(`Codewhale permissions: skipping empty ${source} ${action} pattern.`);
|
|
52667
|
+
return null;
|
|
52668
|
+
}
|
|
52669
|
+
if (!COMMAND_WILDCARD_CHARACTERS.test(trimmed)) return action === "allow" ? {
|
|
52670
|
+
tool: CODEWHALE_SHELL_TOOL,
|
|
52671
|
+
command: trimmed,
|
|
52672
|
+
command_exact: true,
|
|
52673
|
+
action
|
|
52674
|
+
} : {
|
|
52675
|
+
tool: CODEWHALE_SHELL_TOOL,
|
|
52676
|
+
command: trimmed,
|
|
52677
|
+
action
|
|
52678
|
+
};
|
|
52679
|
+
const trailingPrefix = trimmed.endsWith(" *") ? trimmed.slice(0, -2).trim() : null;
|
|
52680
|
+
if (trailingPrefix && !COMMAND_WILDCARD_CHARACTERS.test(trailingPrefix)) return {
|
|
52681
|
+
tool: CODEWHALE_SHELL_TOOL,
|
|
52682
|
+
command: trailingPrefix,
|
|
52683
|
+
action
|
|
52684
|
+
};
|
|
52685
|
+
if (action === "allow") {
|
|
52686
|
+
logger?.warn(`Codewhale permissions: skipping ${source} allow "${pattern}" because Codewhale matches commands by prefix and expands no wildcards.`);
|
|
52687
|
+
return null;
|
|
52688
|
+
}
|
|
52689
|
+
const prefix = literalCommandPrefix(trimmed);
|
|
52690
|
+
if (prefix === "") {
|
|
52691
|
+
logger?.warn(`Codewhale permissions: skipping ${source} ${action} "${pattern}" because it pins down no literal command prefix Codewhale could match.`);
|
|
52692
|
+
return null;
|
|
52693
|
+
}
|
|
52694
|
+
logger?.warn(`Codewhale permissions: broadening ${source} ${action} "${pattern}" to the command prefix "${prefix}" because Codewhale expands no wildcards.`);
|
|
52695
|
+
return {
|
|
52696
|
+
tool: CODEWHALE_SHELL_TOOL,
|
|
52697
|
+
command: prefix,
|
|
52698
|
+
action
|
|
52699
|
+
};
|
|
52700
|
+
}
|
|
52701
|
+
/**
|
|
52702
|
+
* Convert one canonical path rule. Codewhale matches `path` exactly (after
|
|
52703
|
+
* normalizing it against the workspace) and expands no globs, so `*` / `**`
|
|
52704
|
+
* become a tool-wide rule and any other glob is skipped: narrowing it to a
|
|
52705
|
+
* single path would leave the rest of the glob unrestricted, and widening it to
|
|
52706
|
+
* the whole tool would restrict far more than written.
|
|
52707
|
+
*/
|
|
52708
|
+
function convertPathRules({ category, pattern, action, logger }) {
|
|
52709
|
+
const tools = pathCategoryTools(category, action);
|
|
52710
|
+
const trimmed = pattern.trim();
|
|
52711
|
+
if (isCatchAllPattern(trimmed)) return tools.map((tool) => ({
|
|
52712
|
+
tool,
|
|
52713
|
+
action
|
|
52714
|
+
}));
|
|
52715
|
+
if (trimmed === "" || PATH_GLOB_CHARACTERS.test(trimmed)) {
|
|
52716
|
+
logger?.warn(`Codewhale permissions: skipping ${category} ${action} "${pattern}" because Codewhale matches exact paths and expands no globs.`);
|
|
52717
|
+
return [];
|
|
52718
|
+
}
|
|
52719
|
+
return tools.map((tool) => ({
|
|
52720
|
+
tool,
|
|
52721
|
+
path: trimmed,
|
|
52722
|
+
action
|
|
52723
|
+
}));
|
|
52724
|
+
}
|
|
52725
|
+
function ruleKey(rule) {
|
|
52726
|
+
return JSON.stringify([
|
|
52727
|
+
rule.tool,
|
|
52728
|
+
rule.command ?? null,
|
|
52729
|
+
rule.command_exact ?? false,
|
|
52730
|
+
rule.path ?? null,
|
|
52731
|
+
rule.workspace ?? null,
|
|
52732
|
+
rule.action ?? "ask"
|
|
52733
|
+
]);
|
|
52734
|
+
}
|
|
52735
|
+
function withExplicitAction(rule) {
|
|
52736
|
+
return {
|
|
52737
|
+
...rule,
|
|
52738
|
+
action: rule.action ?? "ask"
|
|
52739
|
+
};
|
|
52740
|
+
}
|
|
52741
|
+
/**
|
|
52742
|
+
* Codewhale picks the matching rule by action (`deny` > `ask` > `allow`) before
|
|
52743
|
+
* specificity, so file order never changes a decision. The rules are still
|
|
52744
|
+
* sorted that way, then by tool, so the output is stable and reads in the order
|
|
52745
|
+
* Codewhale applies it.
|
|
52746
|
+
*/
|
|
52747
|
+
function sortRules(rules) {
|
|
52748
|
+
return rules.map((rule, index) => ({
|
|
52749
|
+
rule,
|
|
52750
|
+
index
|
|
52751
|
+
})).toSorted((left, right) => {
|
|
52752
|
+
const actionOrder = PERMISSION_ACTION_PRIORITY[right.rule.action] - PERMISSION_ACTION_PRIORITY[left.rule.action];
|
|
52753
|
+
if (actionOrder !== 0) return actionOrder;
|
|
52754
|
+
const toolOrder = left.rule.tool.localeCompare(right.rule.tool);
|
|
52755
|
+
if (toolOrder !== 0) return toolOrder;
|
|
52756
|
+
return left.index - right.index;
|
|
52757
|
+
}).map(({ rule }) => rule);
|
|
52758
|
+
}
|
|
52759
|
+
function recordDroppedRestriction({ dropped, tools, glob }) {
|
|
52760
|
+
for (const tool of tools) dropped.set(tool, [...dropped.get(tool) ?? [], glob]);
|
|
52761
|
+
}
|
|
52762
|
+
/**
|
|
52763
|
+
* A `[...]` class in a restriction widens to `?` (any one character): matched
|
|
52764
|
+
* after lowercasing, a negated or mixed-case class would otherwise narrow what
|
|
52765
|
+
* the restriction covers.
|
|
52766
|
+
*/
|
|
52767
|
+
function widenRestrictionClasses(pattern) {
|
|
52768
|
+
return pattern.replace(/\[[!^]?\]?[^\]]*\]/g, "?");
|
|
52769
|
+
}
|
|
52770
|
+
/**
|
|
52771
|
+
* An `allow` rule names a literal command or path, so a `[` in it widens to `?`
|
|
52772
|
+
* rather than opening a class that would not even match the rule itself.
|
|
52773
|
+
*/
|
|
52774
|
+
function literalAsGlob(value) {
|
|
52775
|
+
return value.replaceAll("[", "?");
|
|
52776
|
+
}
|
|
52777
|
+
/**
|
|
52778
|
+
* Codewhale matches commands case-insensitively and word by word, so commands
|
|
52779
|
+
* are compared lowercased with their whitespace collapsed.
|
|
52780
|
+
*/
|
|
52781
|
+
function normalizeCommandGlob(glob) {
|
|
52782
|
+
return glob.trim().replace(/\s+/g, " ").toLowerCase();
|
|
52783
|
+
}
|
|
52784
|
+
/**
|
|
52785
|
+
* Codewhale normalizes a rule's path against the workspace, dropping empty and
|
|
52786
|
+
* `.` segments, so the same file can be spelled several ways. Paths are
|
|
52787
|
+
* compared lowercased (for case-insensitive file systems) with those segments
|
|
52788
|
+
* dropped, a `{a,b}` group widens to `*`, a `**` segment widens so it also
|
|
52789
|
+
* matches no directory at all (a leading `**` segment before `*.pem` reaches
|
|
52790
|
+
* `dev.pem`, and `secrets/**` reaches `secrets`), and a path that is absolute,
|
|
52791
|
+
* home-relative or climbs with `..` widens to `*` outright, since it cannot be
|
|
52792
|
+
* compared against a workspace-relative one. Every rewrite only widens a
|
|
52793
|
+
* pattern, so an inexact comparison withholds an `allow` rather than writing
|
|
52794
|
+
* one a dropped restriction overlaps.
|
|
52795
|
+
*/
|
|
52796
|
+
function normalizePathGlob(glob) {
|
|
52797
|
+
const normalized = glob.trim().replaceAll("\\", "/").toLowerCase();
|
|
52798
|
+
if (/^(?:\/|~|[a-z]:)/.test(normalized) || /(?:^|\/)\.\.(?:\/|$)/.test(normalized)) return "*";
|
|
52799
|
+
return normalized.split("/").filter((segment) => segment !== "" && segment !== ".").join("/").replace(/\{.*\}/g, "*").replace(/\*\*\//g, "*").replace(/\/\*\*$/, "*");
|
|
52800
|
+
}
|
|
52801
|
+
/** A dropped `bash` (or all-tools) restriction, as a glob to compare `allow` rules against. */
|
|
52802
|
+
function shellRestrictionGlob(pattern) {
|
|
52803
|
+
return normalizeCommandGlob(widenRestrictionClasses(pattern));
|
|
52804
|
+
}
|
|
52805
|
+
/** A dropped path restriction, as a glob to compare `allow` rules against. */
|
|
52806
|
+
function pathRestrictionGlob(pattern) {
|
|
52807
|
+
return normalizePathGlob(widenRestrictionClasses(pattern));
|
|
52808
|
+
}
|
|
52809
|
+
/**
|
|
52810
|
+
* The file tools whose `path` is the directory a search or listing starts from,
|
|
52811
|
+
* so an `allow` on a directory reaches everything under it.
|
|
52812
|
+
*/
|
|
52813
|
+
const CODEWHALE_DIRECTORY_ROOTED_TOOLS = /* @__PURE__ */ new Set([
|
|
52814
|
+
"grep_files",
|
|
52815
|
+
"file_search",
|
|
52816
|
+
"list_dir"
|
|
52817
|
+
]);
|
|
52818
|
+
/**
|
|
52819
|
+
* The globs a written `allow` rule approves, for comparison with dropped
|
|
52820
|
+
* restrictions. A path that normalizes to the workspace root (`.`) approves
|
|
52821
|
+
* everything, and a directory-rooted tool's path approves what lies under it.
|
|
52822
|
+
*/
|
|
52823
|
+
function allowRuleComparisonGlobs(rule) {
|
|
52824
|
+
if (rule.tool === CODEWHALE_SHELL_TOOL) {
|
|
52825
|
+
if (rule.command === void 0) return ["*"];
|
|
52826
|
+
const command = normalizeCommandGlob(literalAsGlob(rule.command));
|
|
52827
|
+
return rule.command_exact ? [command] : [command, `${command} *`];
|
|
52828
|
+
}
|
|
52829
|
+
const path = rule.path === void 0 ? "" : normalizePathGlob(literalAsGlob(rule.path));
|
|
52830
|
+
if (path === "") return ["*"];
|
|
52831
|
+
return CODEWHALE_DIRECTORY_ROOTED_TOOLS.has(rule.tool) ? [path, `${path}/*`] : [path];
|
|
52832
|
+
}
|
|
52833
|
+
/**
|
|
52834
|
+
* Drop every `allow` rule that a restriction which could not be written
|
|
52835
|
+
* overlaps (any one command or path matches both), so what the restriction
|
|
52836
|
+
* named reaches Codewhale's approval prompt instead of being auto-approved. An
|
|
52837
|
+
* `allow` no dropped restriction overlaps is kept. Comparisons share one budget;
|
|
52838
|
+
* once it runs out every comparison answers "overlaps", so the rule is withheld.
|
|
52839
|
+
*/
|
|
52840
|
+
function withholdShadowedAllowRules({ rules, dropped, logger }) {
|
|
52841
|
+
const budget = createIntersectionBudget();
|
|
52842
|
+
const tests = new Map([...dropped].map(([tool, globs]) => [tool, createShadowingRestrictionsTest(globs.map((pattern) => ({
|
|
52843
|
+
pattern,
|
|
52844
|
+
fromAllToolsCategory: false
|
|
52845
|
+
})), { budget })]));
|
|
52846
|
+
return rules.filter((rule) => {
|
|
52847
|
+
const test = tests.get(rule.tool);
|
|
52848
|
+
if ((rule.action ?? "ask") !== "allow" || test === void 0) return true;
|
|
52849
|
+
const shadowing = [...new Set(allowRuleComparisonGlobs(rule).flatMap((glob) => test(glob)))];
|
|
52850
|
+
if (shadowing.length === 0) return true;
|
|
52851
|
+
logger?.warn(`Codewhale permissions: withholding ${rule.tool} allow ${JSON.stringify(rule.command ?? rule.path ?? "*")}${rule.workspace === void 0 ? "" : ` (workspace ${rule.workspace})`} because the deny/ask rule(s) ${shadowing.map((pattern) => JSON.stringify(pattern)).join(", ")} for ${rule.tool} could not be written; those calls keep Codewhale's approval prompt.`);
|
|
52852
|
+
return false;
|
|
52853
|
+
});
|
|
52854
|
+
}
|
|
52855
|
+
/**
|
|
52856
|
+
* An all-tools `deny` / `ask` names paths as much as commands (`secrets/**`
|
|
52857
|
+
* under `*` denies a path), so it is written for every file tool as well. A
|
|
52858
|
+
* pattern Codewhale cannot match as a path is recorded as dropped for every file
|
|
52859
|
+
* tool instead, the same way a skipped restriction of a path category is.
|
|
52860
|
+
*/
|
|
52861
|
+
function convertAllToolsPathRestriction({ pattern, action, converted, dropped, logger }) {
|
|
52862
|
+
const categories = Object.keys(CODEWHALE_PATH_CATEGORY_TOOLS);
|
|
52863
|
+
const pathRules = categories.flatMap((category) => convertPathRules({
|
|
52864
|
+
category,
|
|
52865
|
+
pattern,
|
|
52866
|
+
action
|
|
52867
|
+
}));
|
|
52868
|
+
if (pathRules.length > 0) {
|
|
52869
|
+
converted.push(...pathRules);
|
|
52870
|
+
return;
|
|
52871
|
+
}
|
|
52872
|
+
logger?.warn(`Codewhale permissions: all-tools ${action} "${pattern}" cannot be written for Codewhale's file tools, which match exact paths and expand no globs.`);
|
|
52873
|
+
for (const category of categories) recordDroppedRestriction({
|
|
52874
|
+
dropped,
|
|
52875
|
+
tools: pathCategoryTools(category, action),
|
|
52876
|
+
glob: pathRestrictionGlob(pattern)
|
|
52877
|
+
});
|
|
52878
|
+
}
|
|
52879
|
+
/**
|
|
52880
|
+
* Convert the rules that govern shell commands (`bash`, plus the restricting
|
|
52881
|
+
* rules of the all-tools `*` category) into `converted`, recording in
|
|
52882
|
+
* `dropped` each restriction that could not be written.
|
|
52883
|
+
*/
|
|
52884
|
+
function convertShellCommandRules({ config, converted, dropped, logger }) {
|
|
52885
|
+
const { rules: shellRules, ignoredAllToolsAllowPatterns } = collectShellCommandRules(config.permission);
|
|
52886
|
+
for (const { pattern, action, fromAllToolsCategory } of shellRules) {
|
|
52887
|
+
const rule = convertShellRule({
|
|
52888
|
+
pattern,
|
|
52889
|
+
action,
|
|
52890
|
+
source: fromAllToolsCategory ? "all-tools (as a shell command)" : SHELL_PERMISSION_CATEGORY,
|
|
52891
|
+
logger
|
|
52892
|
+
});
|
|
52893
|
+
if (rule) converted.push(rule);
|
|
52894
|
+
else if (action !== "allow") recordDroppedRestriction({
|
|
52895
|
+
dropped,
|
|
52896
|
+
tools: [CODEWHALE_SHELL_TOOL],
|
|
52897
|
+
glob: shellRestrictionGlob(pattern)
|
|
52898
|
+
});
|
|
52899
|
+
if (fromAllToolsCategory) convertAllToolsPathRestriction({
|
|
52900
|
+
pattern,
|
|
52901
|
+
action,
|
|
52902
|
+
converted,
|
|
52903
|
+
dropped,
|
|
52904
|
+
logger
|
|
52905
|
+
});
|
|
52906
|
+
}
|
|
52907
|
+
if (ignoredAllToolsAllowPatterns.length > 0) logger?.warn(`Codewhale permissions: skipping all-tools allow rule(s) ${ignoredAllToolsAllowPatterns.map((pattern) => `"${pattern}"`).join(", ")}; an allow under "*" is never widened to Codewhale's tools.`);
|
|
52908
|
+
}
|
|
52909
|
+
/**
|
|
52910
|
+
* Convert the canonical block. The all-tools `*` category contributes its
|
|
52911
|
+
* restricting rules to shell commands (as every command-only adapter reads it,
|
|
52912
|
+
* see `collectShellCommandRules`), and its catch-all `*` restriction to every
|
|
52913
|
+
* file tool as well.
|
|
52914
|
+
*
|
|
52915
|
+
* A `deny` / `ask` Codewhale cannot express is not merely skipped: every
|
|
52916
|
+
* `allow` rule it overlaps for a tool it was meant to restrict is withheld too —
|
|
52917
|
+
* the generated ones and the preserved workspace-scoped grants alike — so the
|
|
52918
|
+
* commands or paths it named reach Codewhale's approval prompt instead of being
|
|
52919
|
+
* auto-approved by a broader `allow` (`{ "*": "allow", "secrets/**": "deny" }`
|
|
52920
|
+
* must not become a bare tool-wide allow). The `codewhale.rules` passthrough is
|
|
52921
|
+
* written as it stands.
|
|
52922
|
+
*/
|
|
52923
|
+
function canonicalToCodewhaleRules({ config, preservedRules, logger }) {
|
|
52924
|
+
const converted = [];
|
|
52925
|
+
const dropped = /* @__PURE__ */ new Map();
|
|
52926
|
+
convertShellCommandRules({
|
|
52927
|
+
config,
|
|
52928
|
+
converted,
|
|
52929
|
+
dropped,
|
|
52930
|
+
logger
|
|
52931
|
+
});
|
|
52932
|
+
for (const [category, rules] of Object.entries(config.permission)) {
|
|
52933
|
+
if (category === "bash" || category === "*") continue;
|
|
52934
|
+
if (CODEWHALE_PATH_CATEGORY_TOOLS[category] === void 0) {
|
|
52935
|
+
if (Object.keys(rules).length > 0) logger?.warn(`Codewhale permissions: skipping unsupported category "${category}"; Codewhale evaluates permission rules only for shell commands and file tools.`);
|
|
52936
|
+
continue;
|
|
52937
|
+
}
|
|
52938
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
52939
|
+
const pathRules = convertPathRules({
|
|
52940
|
+
category,
|
|
52941
|
+
pattern,
|
|
52942
|
+
action,
|
|
52943
|
+
logger
|
|
52944
|
+
});
|
|
52945
|
+
converted.push(...pathRules);
|
|
52946
|
+
if (pathRules.length === 0 && action !== "allow") recordDroppedRestriction({
|
|
52947
|
+
dropped,
|
|
52948
|
+
tools: pathCategoryTools(category, action),
|
|
52949
|
+
glob: pathRestrictionGlob(pattern)
|
|
52950
|
+
});
|
|
52951
|
+
}
|
|
52952
|
+
}
|
|
52953
|
+
const kept = withholdShadowedAllowRules({
|
|
52954
|
+
rules: converted,
|
|
52955
|
+
dropped,
|
|
52956
|
+
logger
|
|
52957
|
+
});
|
|
52958
|
+
const keptPreserved = withholdShadowedAllowRules({
|
|
52959
|
+
rules: preservedRules,
|
|
52960
|
+
dropped,
|
|
52961
|
+
logger
|
|
52962
|
+
});
|
|
52963
|
+
return [
|
|
52964
|
+
...(config.codewhale?.rules ?? []).map(withExplicitAction),
|
|
52965
|
+
...sortRules(kept),
|
|
52966
|
+
...keptPreserved
|
|
52967
|
+
];
|
|
52968
|
+
}
|
|
52969
|
+
function parsePermissionsDocument({ fileContent, filePath }) {
|
|
52970
|
+
if (fileContent.trim() === "") return {};
|
|
52971
|
+
try {
|
|
52972
|
+
return smol_toml.parse(fileContent);
|
|
52973
|
+
} catch (error) {
|
|
52974
|
+
throw new Error(`Failed to parse existing Codewhale permissions at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
52975
|
+
}
|
|
52976
|
+
}
|
|
52977
|
+
/**
|
|
52978
|
+
* The workspace-scoped rules already in the file. Codewhale's approval card
|
|
52979
|
+
* appends one ("Always allow this exact rule in this repo") with the absolute
|
|
52980
|
+
* `workspace` it was granted in, and rulesync never derives a `workspace` from
|
|
52981
|
+
* the canonical block, so these are remembered grants rather than earlier
|
|
52982
|
+
* rulesync output and survive a regenerate. A record Codewhale would reject is
|
|
52983
|
+
* not carried over, since it would make Codewhale refuse the whole file.
|
|
52984
|
+
*/
|
|
52985
|
+
function collectWorkspaceScopedRules({ document, logger }) {
|
|
52986
|
+
const { valid, invalidCount } = partitionRuleRecords(document);
|
|
52987
|
+
if (invalidCount > 0) logger?.warn(`Codewhale permissions: dropping ${invalidCount} existing rule record(s) Codewhale would reject (an unknown key or a wrong type).`);
|
|
52988
|
+
return valid.filter((rule) => rule.workspace !== void 0);
|
|
52989
|
+
}
|
|
52990
|
+
/**
|
|
52991
|
+
* Codewhale reads `permissions.toml` next to the `config.toml` in use, which
|
|
52992
|
+
* `CODEWHALE_HOME` or `CODEWHALE_CONFIG_PATH` can move away from
|
|
52993
|
+
* `~/.codewhale/`. rulesync always writes `~/.codewhale/permissions.toml`, so
|
|
52994
|
+
* say so when either is set rather than let the rules go unread.
|
|
52995
|
+
*/
|
|
52996
|
+
function warnAboutRelocatedConfig(logger) {
|
|
52997
|
+
for (const name of ["CODEWHALE_HOME", "CODEWHALE_CONFIG_PATH"]) if (process.env[name]) logger?.warn(`Codewhale permissions: ${name} is set, but rulesync only syncs ~/.codewhale/permissions.toml; Codewhale reads permissions.toml next to the config.toml in use.`);
|
|
52998
|
+
}
|
|
52999
|
+
/** The records of a parsed file that Codewhale itself would accept. */
|
|
53000
|
+
function partitionRuleRecords(document) {
|
|
53001
|
+
const rawRules = Array.isArray(document.rules) ? document.rules : [];
|
|
53002
|
+
const valid = [];
|
|
53003
|
+
let invalidCount = 0;
|
|
53004
|
+
for (const raw of rawRules) {
|
|
53005
|
+
const parsed = CodewhalePermissionRuleSchema.safeParse(raw);
|
|
53006
|
+
if (parsed.success) valid.push(parsed.data);
|
|
53007
|
+
else invalidCount += 1;
|
|
53008
|
+
}
|
|
53009
|
+
return {
|
|
53010
|
+
valid,
|
|
53011
|
+
invalidCount
|
|
53012
|
+
};
|
|
53013
|
+
}
|
|
53014
|
+
/**
|
|
53015
|
+
* The canonical `bash` pattern a native shell rule round-trips through, or
|
|
53016
|
+
* `null` when the next generate would not write the same rule back from it.
|
|
53017
|
+
*/
|
|
53018
|
+
function shellRuleToPattern(rule) {
|
|
53019
|
+
if (rule.path !== void 0) return null;
|
|
53020
|
+
if (rule.command === void 0) return rule.command_exact ? null : "*";
|
|
53021
|
+
const command = rule.command.trim();
|
|
53022
|
+
if (command === "" || COMMAND_WILDCARD_CHARACTERS.test(command)) return null;
|
|
53023
|
+
if (rule.command_exact) return rule.action === "allow" ? command : null;
|
|
53024
|
+
return `${command} *`;
|
|
53025
|
+
}
|
|
53026
|
+
function pathRuleToPattern(rule) {
|
|
53027
|
+
if (rule.command !== void 0 || rule.command_exact) return null;
|
|
53028
|
+
if (rule.path === void 0) return "*";
|
|
53029
|
+
const path = rule.path.trim();
|
|
53030
|
+
return path === "" || PATH_GLOB_CHARACTERS.test(path) ? null : path;
|
|
53031
|
+
}
|
|
53032
|
+
function toImportCandidates(rule) {
|
|
53033
|
+
if (rule.workspace !== void 0) return [];
|
|
53034
|
+
if (rule.tool === CODEWHALE_SHELL_TOOL) {
|
|
53035
|
+
const pattern = shellRuleToPattern(rule);
|
|
53036
|
+
return pattern === null ? [] : [{
|
|
53037
|
+
category: SHELL_PERMISSION_CATEGORY,
|
|
53038
|
+
pattern,
|
|
53039
|
+
rule
|
|
53040
|
+
}];
|
|
53041
|
+
}
|
|
53042
|
+
const pattern = pathRuleToPattern(rule);
|
|
53043
|
+
if (pattern === null) return [];
|
|
53044
|
+
return (CODEWHALE_TOOL_TO_PATH_CATEGORIES[rule.tool] ?? []).map((category) => ({
|
|
53045
|
+
category,
|
|
53046
|
+
pattern,
|
|
53047
|
+
rule
|
|
53048
|
+
}));
|
|
53049
|
+
}
|
|
53050
|
+
/**
|
|
53051
|
+
* Split the native rules into the canonical block and the `codewhale.rules`
|
|
53052
|
+
* passthrough so that the next generate writes the same file back. A canonical
|
|
53053
|
+
* pattern is taken only when every tool its category expands to carries the
|
|
53054
|
+
* same rule (`edit` needs both `edit_file` and `apply_patch`, a `write`
|
|
53055
|
+
* restriction both `write_file` and `apply_patch`). A rule no canonical pattern
|
|
53056
|
+
* claims — including the weaker of two rules for one pattern — stays native.
|
|
53057
|
+
*/
|
|
53058
|
+
function codewhaleRulesToCanonical(rules) {
|
|
53059
|
+
const permission = {};
|
|
53060
|
+
const claimedIndexes = /* @__PURE__ */ new Set();
|
|
53061
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
53062
|
+
for (const [index, rule] of rules.entries()) for (const candidate of toImportCandidates(rule)) {
|
|
53063
|
+
const key = JSON.stringify([
|
|
53064
|
+
candidate.category,
|
|
53065
|
+
candidate.pattern,
|
|
53066
|
+
rule.action
|
|
53067
|
+
]);
|
|
53068
|
+
grouped.set(key, [...grouped.get(key) ?? [], {
|
|
53069
|
+
...candidate,
|
|
53070
|
+
index
|
|
53071
|
+
}]);
|
|
53072
|
+
}
|
|
53073
|
+
const strongestFirst = [...grouped.values()].toSorted((left, right) => PERMISSION_ACTION_PRIORITY[right[0].rule.action] - PERMISSION_ACTION_PRIORITY[left[0].rule.action]);
|
|
53074
|
+
for (const group of strongestFirst) {
|
|
53075
|
+
const { category, pattern, rule } = group[0];
|
|
53076
|
+
const expectedTools = category === "bash" ? [CODEWHALE_SHELL_TOOL] : pathCategoryTools(category, rule.action);
|
|
53077
|
+
const presentTools = new Set(group.map((candidate) => candidate.rule.tool));
|
|
53078
|
+
const complete = expectedTools.every((tool) => presentTools.has(tool));
|
|
53079
|
+
const categoryRules = permission[category] ?? {};
|
|
53080
|
+
if (!complete || Object.hasOwn(categoryRules, pattern)) continue;
|
|
53081
|
+
permission[category] = {
|
|
53082
|
+
...categoryRules,
|
|
53083
|
+
[pattern]: rule.action
|
|
53084
|
+
};
|
|
53085
|
+
for (const candidate of group) if (expectedTools.includes(candidate.rule.tool)) claimedIndexes.add(candidate.index);
|
|
53086
|
+
}
|
|
53087
|
+
return {
|
|
53088
|
+
permission,
|
|
53089
|
+
nativeRules: rules.filter((_rule, index) => !claimedIndexes.has(index))
|
|
53090
|
+
};
|
|
53091
|
+
}
|
|
53092
|
+
/**
|
|
53093
|
+
* Codewhale permission rules in `~/.codewhale/permissions.toml`.
|
|
53094
|
+
*
|
|
53095
|
+
* The file holds a `rules` array of typed records (`tool`, optional `command`
|
|
53096
|
+
* prefix or exact `path`, optional `command_exact`, optional absolute
|
|
53097
|
+
* `workspace`, and `action`). It is the only permission-rule source Codewhale
|
|
53098
|
+
* reads — the project `.codewhale/config.toml` overlay cannot carry rules — so
|
|
53099
|
+
* the feature is global-only.
|
|
53100
|
+
*
|
|
53101
|
+
* rulesync owns the file's rules, except for workspace-scoped ones: those are
|
|
53102
|
+
* the grants Codewhale's approval card remembers per repository, and they are
|
|
53103
|
+
* kept across a regenerate. Codewhale also appends plain `exec_shell` ask rules
|
|
53104
|
+
* from the approval card (`S`), which cannot be told apart from rulesync's own
|
|
53105
|
+
* output and are replaced; run an import first to keep them.
|
|
53106
|
+
*
|
|
53107
|
+
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/CONFIGURATION.md
|
|
53108
|
+
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/AUTHORIZATION_ORDER.md
|
|
53109
|
+
*/
|
|
53110
|
+
var CodewhalePermissions = class CodewhalePermissions extends ToolPermissions {
|
|
53111
|
+
constructor(params) {
|
|
53112
|
+
super({
|
|
53113
|
+
...params,
|
|
53114
|
+
fileContent: params.fileContent ?? ""
|
|
53115
|
+
});
|
|
53116
|
+
}
|
|
53117
|
+
/**
|
|
53118
|
+
* Codewhale's approval card writes to the same file, so this feature never
|
|
53119
|
+
* deletes it.
|
|
53120
|
+
*/
|
|
53121
|
+
isDeletable() {
|
|
53122
|
+
return false;
|
|
53123
|
+
}
|
|
53124
|
+
shouldSkipCreationWhenPayloadEmpty() {
|
|
53125
|
+
return true;
|
|
53126
|
+
}
|
|
53127
|
+
static getSettablePaths(_options) {
|
|
53128
|
+
return {
|
|
53129
|
+
relativeDirPath: CODEWHALE_DIR,
|
|
53130
|
+
relativeFilePath: CODEWHALE_PERMISSIONS_FILE_NAME
|
|
53131
|
+
};
|
|
53132
|
+
}
|
|
53133
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false, logger }) {
|
|
53134
|
+
if (!global) throw new Error(CODEWHALE_GLOBAL_ONLY_MESSAGE);
|
|
53135
|
+
warnAboutRelocatedConfig(logger);
|
|
53136
|
+
const paths = CodewhalePermissions.getSettablePaths();
|
|
53137
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
53138
|
+
const fileContent = await readFileContentOrNull(filePath) ?? "";
|
|
53139
|
+
const { invalidCount } = partitionRuleRecords(parsePermissionsDocument({
|
|
53140
|
+
fileContent,
|
|
53141
|
+
filePath
|
|
53142
|
+
}));
|
|
53143
|
+
if (invalidCount > 0) logger?.warn(`Codewhale permissions: skipping ${invalidCount} rule record(s) in ${filePath} that Codewhale would reject (an unknown key or a wrong type).`);
|
|
53144
|
+
return new CodewhalePermissions({
|
|
53145
|
+
outputRoot,
|
|
53146
|
+
relativeDirPath: paths.relativeDirPath,
|
|
53147
|
+
relativeFilePath: paths.relativeFilePath,
|
|
53148
|
+
fileContent,
|
|
53149
|
+
validate,
|
|
53150
|
+
global: true
|
|
53151
|
+
});
|
|
53152
|
+
}
|
|
53153
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
53154
|
+
if (!global) throw new Error(CODEWHALE_GLOBAL_ONLY_MESSAGE);
|
|
53155
|
+
warnAboutRelocatedConfig(logger);
|
|
53156
|
+
const paths = CodewhalePermissions.getSettablePaths();
|
|
53157
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
53158
|
+
const existing = parsePermissionsDocument({
|
|
53159
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
53160
|
+
filePath
|
|
53161
|
+
});
|
|
53162
|
+
const generated = canonicalToCodewhaleRules({
|
|
53163
|
+
config: rulesyncPermissions.getJson(),
|
|
53164
|
+
preservedRules: collectWorkspaceScopedRules({
|
|
53165
|
+
document: existing,
|
|
53166
|
+
logger
|
|
53167
|
+
}),
|
|
53168
|
+
logger
|
|
53169
|
+
});
|
|
53170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
53171
|
+
const rules = [];
|
|
53172
|
+
for (const rule of generated) {
|
|
53173
|
+
const key = ruleKey(rule);
|
|
53174
|
+
if (seen.has(key)) continue;
|
|
53175
|
+
seen.add(key);
|
|
53176
|
+
rules.push(rule);
|
|
53177
|
+
}
|
|
53178
|
+
return new CodewhalePermissions({
|
|
53179
|
+
outputRoot,
|
|
53180
|
+
relativeDirPath: paths.relativeDirPath,
|
|
53181
|
+
relativeFilePath: paths.relativeFilePath,
|
|
53182
|
+
fileContent: rules.length > 0 ? smol_toml.stringify({ rules }) : "",
|
|
53183
|
+
validate: true,
|
|
53184
|
+
global: true
|
|
53185
|
+
});
|
|
53186
|
+
}
|
|
53187
|
+
toRulesyncPermissions() {
|
|
53188
|
+
const { permission, nativeRules } = codewhaleRulesToCanonical(partitionRuleRecords(parsePermissionsDocument({
|
|
53189
|
+
fileContent: this.getFileContent(),
|
|
53190
|
+
filePath: (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())
|
|
53191
|
+
})).valid.map(withExplicitAction));
|
|
53192
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
53193
|
+
permission,
|
|
53194
|
+
...nativeRules.length > 0 && { codewhale: { rules: nativeRules } }
|
|
53195
|
+
}, null, 2) });
|
|
53196
|
+
}
|
|
53197
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
53198
|
+
return new CodewhalePermissions({
|
|
53199
|
+
outputRoot,
|
|
53200
|
+
relativeDirPath,
|
|
53201
|
+
relativeFilePath,
|
|
53202
|
+
fileContent: "",
|
|
53203
|
+
validate: false,
|
|
53204
|
+
global: true
|
|
53205
|
+
});
|
|
53206
|
+
}
|
|
53207
|
+
};
|
|
53208
|
+
//#endregion
|
|
52320
53209
|
//#region src/features/permissions/codexcli-permissions.ts
|
|
52321
53210
|
const RULESYNC_PROFILE_NAME = "rulesync";
|
|
52322
53211
|
const CODEX_WORKSPACE_ROOTS_KEY = ":workspace_roots";
|
|
@@ -53192,7 +54081,7 @@ function mapBashActionToDecision(action) {
|
|
|
53192
54081
|
//#region src/features/permissions/commandcode-permissions.ts
|
|
53193
54082
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
53194
54083
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
53195
|
-
const CATCH_ALL_PATTERN$
|
|
54084
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
53196
54085
|
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
53197
54086
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
53198
54087
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
@@ -53247,10 +54136,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
53247
54136
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
53248
54137
|
*/
|
|
53249
54138
|
function buildCommandcodeRule(category, pattern) {
|
|
53250
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
54139
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$10 || pattern === "";
|
|
53251
54140
|
if (category.startsWith(MCP_CANONICAL_PREFIX$6)) return catchAll ? category : `${category}(${pattern})`;
|
|
53252
54141
|
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$6}${pattern}`;
|
|
53253
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
54142
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$10 : null;
|
|
53254
54143
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
53255
54144
|
if (tool === void 0) return null;
|
|
53256
54145
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -53286,7 +54175,7 @@ function splitCommandcodeRule(rule) {
|
|
|
53286
54175
|
}
|
|
53287
54176
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
53288
54177
|
function isCatchAllSpecifier(inner) {
|
|
53289
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
54178
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$10;
|
|
53290
54179
|
}
|
|
53291
54180
|
/**
|
|
53292
54181
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -53320,34 +54209,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
53320
54209
|
if (server.length === 0) return null;
|
|
53321
54210
|
const everyServer = {
|
|
53322
54211
|
category: "mcp",
|
|
53323
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54212
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53324
54213
|
notInAllow: true
|
|
53325
54214
|
};
|
|
53326
54215
|
if (tool.startsWith(MCP_CANONICAL_PREFIX$6)) {
|
|
53327
|
-
if (server === CATCH_ALL_PATTERN$
|
|
53328
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
54216
|
+
if (server === CATCH_ALL_PATTERN$10) return everyServer;
|
|
54217
|
+
if (server.includes(CATCH_ALL_PATTERN$10)) return null;
|
|
53329
54218
|
return {
|
|
53330
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
53331
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54219
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$10 ? `${MCP_CANONICAL_PREFIX$6}${server}` : `${MCP_CANONICAL_PREFIX$6}${remainder}`,
|
|
54220
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53332
54221
|
notInAllow: false
|
|
53333
54222
|
};
|
|
53334
54223
|
}
|
|
53335
54224
|
const category = `${MCP_CANONICAL_PREFIX$6}${remainder}`;
|
|
53336
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
54225
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$10);
|
|
53337
54226
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
53338
54227
|
category,
|
|
53339
54228
|
pattern: inner,
|
|
53340
54229
|
notInAllow: true
|
|
53341
54230
|
} : null;
|
|
53342
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
53343
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
54231
|
+
if (remainder === CATCH_ALL_PATTERN$10) return everyServer;
|
|
54232
|
+
if (rest === CATCH_ALL_PATTERN$10 && !server.includes(CATCH_ALL_PATTERN$10)) return {
|
|
53344
54233
|
category: `${MCP_CANONICAL_PREFIX$6}${server}`,
|
|
53345
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54234
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53346
54235
|
notInAllow: true
|
|
53347
54236
|
};
|
|
53348
54237
|
return namesTool ? {
|
|
53349
54238
|
category,
|
|
53350
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54239
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53351
54240
|
notInAllow: false
|
|
53352
54241
|
} : null;
|
|
53353
54242
|
}
|
|
@@ -53371,14 +54260,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
53371
54260
|
if (category === "bash") {
|
|
53372
54261
|
if (isCatchAllSpecifier(inner)) return {
|
|
53373
54262
|
category,
|
|
53374
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54263
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53375
54264
|
notInAllow: false
|
|
53376
54265
|
};
|
|
53377
54266
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
53378
54267
|
if (normalized === "" || normalized === ":*") return null;
|
|
53379
54268
|
return isCatchAllSpecifier(normalized) ? {
|
|
53380
54269
|
category,
|
|
53381
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54270
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53382
54271
|
notInAllow: true
|
|
53383
54272
|
} : {
|
|
53384
54273
|
category,
|
|
@@ -53388,7 +54277,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
53388
54277
|
}
|
|
53389
54278
|
if (isCatchAllSpecifier(inner)) return {
|
|
53390
54279
|
category,
|
|
53391
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54280
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53392
54281
|
notInAllow: false
|
|
53393
54282
|
};
|
|
53394
54283
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -53407,9 +54296,9 @@ function friendlyToolRule({ category, inner }) {
|
|
|
53407
54296
|
*/
|
|
53408
54297
|
function parseCommandcodeRule(rule) {
|
|
53409
54298
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
53410
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
54299
|
+
if (tool === CATCH_ALL_PATTERN$10) return isCatchAllSpecifier(inner) ? {
|
|
53411
54300
|
category: "*",
|
|
53412
|
-
pattern: CATCH_ALL_PATTERN$
|
|
54301
|
+
pattern: CATCH_ALL_PATTERN$10,
|
|
53413
54302
|
notInAllow: true
|
|
53414
54303
|
} : null;
|
|
53415
54304
|
const lowered = tool.toLowerCase();
|
|
@@ -53479,7 +54368,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
53479
54368
|
}).filter((rule) => {
|
|
53480
54369
|
const parsed = parseCommandcodeRule(rule);
|
|
53481
54370
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
53482
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
54371
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$10})`);
|
|
53483
54372
|
if (key !== "allow" && isStricterAction({
|
|
53484
54373
|
action: key,
|
|
53485
54374
|
existing: replacement
|
|
@@ -53536,7 +54425,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
53536
54425
|
const ranked = /* @__PURE__ */ new Map();
|
|
53537
54426
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
53538
54427
|
for (const [category, rules] of Object.entries(permission)) {
|
|
53539
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
54428
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$10);
|
|
53540
54429
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
53541
54430
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
53542
54431
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -53807,7 +54696,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
53807
54696
|
//#endregion
|
|
53808
54697
|
//#region src/features/permissions/continue-permissions.ts
|
|
53809
54698
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
53810
|
-
const CATCH_ALL_PATTERN$
|
|
54699
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
53811
54700
|
/**
|
|
53812
54701
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
53813
54702
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -53815,7 +54704,7 @@ const CATCH_ALL_PATTERN$8 = "*";
|
|
|
53815
54704
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
53816
54705
|
*/
|
|
53817
54706
|
function normalizeCatchAllPattern$1(pattern) {
|
|
53818
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
54707
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$9 : pattern;
|
|
53819
54708
|
}
|
|
53820
54709
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
53821
54710
|
bash: "Bash",
|
|
@@ -53852,7 +54741,7 @@ function toCanonicalToolName$6(continueName) {
|
|
|
53852
54741
|
* `Tool(pattern)`.
|
|
53853
54742
|
*/
|
|
53854
54743
|
function buildContinuePermissionEntry(toolName, pattern) {
|
|
53855
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
54744
|
+
return pattern === CATCH_ALL_PATTERN$9 ? toolName : `${toolName}(${pattern})`;
|
|
53856
54745
|
}
|
|
53857
54746
|
/**
|
|
53858
54747
|
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
@@ -53866,7 +54755,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
53866
54755
|
if (toolName === "") return;
|
|
53867
54756
|
return {
|
|
53868
54757
|
toolName,
|
|
53869
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
54758
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$9 : pattern
|
|
53870
54759
|
};
|
|
53871
54760
|
}
|
|
53872
54761
|
/**
|
|
@@ -54040,7 +54929,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
54040
54929
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
54041
54930
|
continue;
|
|
54042
54931
|
}
|
|
54043
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
54932
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$9) {
|
|
54044
54933
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
54045
54934
|
continue;
|
|
54046
54935
|
}
|
|
@@ -54389,14 +55278,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
54389
55278
|
};
|
|
54390
55279
|
//#endregion
|
|
54391
55280
|
//#region src/features/permissions/crush-permissions.ts
|
|
54392
|
-
const CATCH_ALL_PATTERN$
|
|
55281
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
54393
55282
|
/**
|
|
54394
55283
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
54395
55284
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
54396
55285
|
* still be written.
|
|
54397
55286
|
*/
|
|
54398
55287
|
function normalizeCatchAllPattern(pattern) {
|
|
54399
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
55288
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$8 : pattern;
|
|
54400
55289
|
}
|
|
54401
55290
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
54402
55291
|
bash: "bash",
|
|
@@ -54595,7 +55484,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
54595
55484
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
54596
55485
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
54597
55486
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
54598
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
55487
|
+
if (pattern === CATCH_ALL_PATTERN$8) {
|
|
54599
55488
|
catchAllAction = action;
|
|
54600
55489
|
continue;
|
|
54601
55490
|
}
|
|
@@ -54668,11 +55557,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
54668
55557
|
};
|
|
54669
55558
|
for (const entry of disabled) {
|
|
54670
55559
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
54671
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
55560
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$8] = "deny";
|
|
54672
55561
|
}
|
|
54673
55562
|
for (const entry of allowed) {
|
|
54674
55563
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
54675
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
55564
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$8] ??= "allow";
|
|
54676
55565
|
}
|
|
54677
55566
|
return { permission };
|
|
54678
55567
|
}
|
|
@@ -56537,7 +57426,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
56537
57426
|
//#region src/features/permissions/goose-permissions.ts
|
|
56538
57427
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
56539
57428
|
const GOOSE_USER_KEY = "user";
|
|
56540
|
-
const CATCH_ALL_PATTERN$
|
|
57429
|
+
const CATCH_ALL_PATTERN$7 = "*";
|
|
56541
57430
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
56542
57431
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
56543
57432
|
shell: ["bash"],
|
|
@@ -56701,7 +57590,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
56701
57590
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
56702
57591
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
56703
57592
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
56704
|
-
if (pattern !== CATCH_ALL_PATTERN$
|
|
57593
|
+
if (pattern !== CATCH_ALL_PATTERN$7) {
|
|
56705
57594
|
logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
|
|
56706
57595
|
continue;
|
|
56707
57596
|
}
|
|
@@ -56732,7 +57621,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
56732
57621
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
56733
57622
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
56734
57623
|
permission[category] ??= {};
|
|
56735
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
57624
|
+
permission[category][CATCH_ALL_PATTERN$7] = action;
|
|
56736
57625
|
}
|
|
56737
57626
|
}
|
|
56738
57627
|
return { permission };
|
|
@@ -56743,7 +57632,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
56743
57632
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
56744
57633
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
56745
57634
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
56746
|
-
const CATCH_ALL_PATTERN$
|
|
57635
|
+
const CATCH_ALL_PATTERN$6 = "*";
|
|
56747
57636
|
const MCP_CANONICAL_PREFIX$3 = "mcp__";
|
|
56748
57637
|
const CATEGORY_TO_GROK_TOOL = {
|
|
56749
57638
|
bash: "Bash",
|
|
@@ -56789,10 +57678,10 @@ function buildGrokEntry(category, pattern) {
|
|
|
56789
57678
|
const remainder = category.slice(5);
|
|
56790
57679
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
56791
57680
|
}
|
|
56792
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
57681
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$6 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
56793
57682
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
56794
57683
|
if (tool === void 0) return null;
|
|
56795
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
57684
|
+
return pattern === CATCH_ALL_PATTERN$6 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
56796
57685
|
}
|
|
56797
57686
|
/**
|
|
56798
57687
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
@@ -56813,16 +57702,16 @@ function parseGrokEntry(entry) {
|
|
|
56813
57702
|
}
|
|
56814
57703
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
56815
57704
|
category: `${MCP_CANONICAL_PREFIX$3}${inner}`,
|
|
56816
|
-
pattern: CATCH_ALL_PATTERN$
|
|
57705
|
+
pattern: CATCH_ALL_PATTERN$6
|
|
56817
57706
|
} : {
|
|
56818
57707
|
category: "mcp",
|
|
56819
|
-
pattern: CATCH_ALL_PATTERN$
|
|
57708
|
+
pattern: CATCH_ALL_PATTERN$6
|
|
56820
57709
|
};
|
|
56821
57710
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
56822
57711
|
if (category === void 0) return null;
|
|
56823
57712
|
return {
|
|
56824
57713
|
category,
|
|
56825
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
57714
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$6
|
|
56826
57715
|
};
|
|
56827
57716
|
}
|
|
56828
57717
|
/**
|
|
@@ -56843,15 +57732,15 @@ function parseGrokRule(rule) {
|
|
|
56843
57732
|
if (typeof tool !== "string") return null;
|
|
56844
57733
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
56845
57734
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
56846
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
57735
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$6;
|
|
56847
57736
|
const lowerTool = tool.trim().toLowerCase();
|
|
56848
|
-
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
57737
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$6 ? {
|
|
56849
57738
|
category: "mcp",
|
|
56850
|
-
pattern: CATCH_ALL_PATTERN$
|
|
57739
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
56851
57740
|
action
|
|
56852
57741
|
} : {
|
|
56853
57742
|
category: `${MCP_CANONICAL_PREFIX$3}${resolvedPattern}`,
|
|
56854
|
-
pattern: CATCH_ALL_PATTERN$
|
|
57743
|
+
pattern: CATCH_ALL_PATTERN$6,
|
|
56855
57744
|
action
|
|
56856
57745
|
};
|
|
56857
57746
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -56999,7 +57888,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
56999
57888
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
57000
57889
|
}
|
|
57001
57890
|
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
57002
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
57891
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$6]: legacyModeAction(parsed) } } };
|
|
57003
57892
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
57004
57893
|
}
|
|
57005
57894
|
validate() {
|
|
@@ -57185,12 +58074,12 @@ function reconcileNativeDenies({ permission, category, patterns }) {
|
|
|
57185
58074
|
deleteRulesByAction(rules, "deny");
|
|
57186
58075
|
for (const pattern of patterns) rules[pattern] = "deny";
|
|
57187
58076
|
}
|
|
57188
|
-
function withoutKey(record, key) {
|
|
58077
|
+
function withoutKey$1(record, key) {
|
|
57189
58078
|
return Object.fromEntries(Object.entries(record).filter(([entryKey]) => entryKey !== key));
|
|
57190
58079
|
}
|
|
57191
58080
|
function buildHermesOverride(config, provenance) {
|
|
57192
58081
|
const base = isRecord$1(provenance.hermes) ? { ...provenance.hermes } : {};
|
|
57193
|
-
const approvalsOverride = withoutKey(isRecord$1(config.approvals) ? config.approvals : {}, "deny");
|
|
58082
|
+
const approvalsOverride = withoutKey$1(isRecord$1(config.approvals) ? config.approvals : {}, "deny");
|
|
57194
58083
|
if (Object.keys(approvalsOverride).length > 0) base.approvals = approvalsOverride;
|
|
57195
58084
|
else delete base.approvals;
|
|
57196
58085
|
const security = isRecord$1(config.security) ? { ...config.security } : {};
|
|
@@ -57775,6 +58664,35 @@ function narrowSandboxToProjectScope({ authored, logger }) {
|
|
|
57775
58664
|
if (dropped.length > 0) logger?.warn(`Kilo honors these 'sandbox' keys from the global config only, so they were dropped from the project config: ${dropped.toSorted().join(", ")}. A project config may only tighten the sandbox ('enabled', 'network'); generate with --global to author the rest.`);
|
|
57776
58665
|
return emitted;
|
|
57777
58666
|
}
|
|
58667
|
+
const KILO_MARKDOWN_SOURCE_KEY = "markdown_source";
|
|
58668
|
+
/**
|
|
58669
|
+
* Narrow an authored `markdown_source` rule to what a *project* `kilo.jsonc`
|
|
58670
|
+
* can mean. Kilo only grants it when the winning pattern came from the global
|
|
58671
|
+
* config with the action `allow` ("Project configuration cannot grant this
|
|
58672
|
+
* permission"). A project pattern that wins last-match-wins therefore never
|
|
58673
|
+
* grants: `deny` and `ask` both block (Kilo never prompts for it), so they
|
|
58674
|
+
* are kept — a project config may only tighten. A project `allow` would block
|
|
58675
|
+
* too, the opposite of what it says, so it is dropped with a warning pointing
|
|
58676
|
+
* at `--global`.
|
|
58677
|
+
* Returns `undefined` when nothing is left to write.
|
|
58678
|
+
* @see https://kilo.ai/docs/customize/workflows
|
|
58679
|
+
* @see https://github.com/Kilo-Org/kilocode/blob/main/packages/opencode/src/kilocode/config/external-markdown.ts
|
|
58680
|
+
*/
|
|
58681
|
+
function narrowMarkdownSourceToProjectScope({ authored, logger }) {
|
|
58682
|
+
let emitted;
|
|
58683
|
+
let dropped;
|
|
58684
|
+
if (typeof authored === "string") {
|
|
58685
|
+
emitted = authored === "allow" ? void 0 : authored;
|
|
58686
|
+
dropped = authored === "allow" ? ["*"] : [];
|
|
58687
|
+
} else {
|
|
58688
|
+
const entries = Object.entries(asKiloRecord(authored));
|
|
58689
|
+
const kept = entries.filter(([, action]) => action !== "allow");
|
|
58690
|
+
emitted = kept.length > 0 ? Object.fromEntries(kept) : void 0;
|
|
58691
|
+
dropped = entries.filter(([, action]) => action === "allow").map(([pattern]) => pattern);
|
|
58692
|
+
}
|
|
58693
|
+
if (dropped.length > 0) logger?.warn(`Kilo grants '${KILO_MARKDOWN_SOURCE_KEY}' from the global config only, so these 'allow' patterns were dropped from the project config: ${dropped.join(", ")}. A project config can only block it ('deny' or 'ask'); generate with --global to grant access.`);
|
|
58694
|
+
return emitted;
|
|
58695
|
+
}
|
|
57778
58696
|
var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
57779
58697
|
json;
|
|
57780
58698
|
constructor(params) {
|
|
@@ -57861,6 +58779,13 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
57861
58779
|
...honorAllToolsOnBash(rulesyncJson.permission),
|
|
57862
58780
|
...kiloOverride?.permission
|
|
57863
58781
|
};
|
|
58782
|
+
if (!global && Object.hasOwn(incomingPermission, KILO_MARKDOWN_SOURCE_KEY)) {
|
|
58783
|
+
const narrowed = narrowMarkdownSourceToProjectScope({
|
|
58784
|
+
authored: incomingPermission[KILO_MARKDOWN_SOURCE_KEY],
|
|
58785
|
+
logger
|
|
58786
|
+
});
|
|
58787
|
+
incomingPermission[KILO_MARKDOWN_SOURCE_KEY] = narrowed;
|
|
58788
|
+
}
|
|
57864
58789
|
const droppedDenyByKey = {};
|
|
57865
58790
|
for (const [key, value] of Object.entries(incomingPermission)) {
|
|
57866
58791
|
const previousDenyPatterns = collectKiloDenyPatterns(existingPermission[key]);
|
|
@@ -57876,6 +58801,7 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
57876
58801
|
...existingPermission,
|
|
57877
58802
|
...incomingPermission
|
|
57878
58803
|
};
|
|
58804
|
+
if (mergedPermission[KILO_MARKDOWN_SOURCE_KEY] === void 0) delete mergedPermission[KILO_MARKDOWN_SOURCE_KEY];
|
|
57879
58805
|
const nextJson = {
|
|
57880
58806
|
...parsed,
|
|
57881
58807
|
permission: mergedPermission
|
|
@@ -58552,7 +59478,7 @@ function rulesFromArrays(settings, allowKey, denyKey) {
|
|
|
58552
59478
|
//#region src/features/permissions/lettacode-permissions.ts
|
|
58553
59479
|
/** Top-level key of `.letta/settings.json` that rulesync owns. */
|
|
58554
59480
|
const LETTACODE_PERMISSIONS_KEY = "permissions";
|
|
58555
|
-
const CATCH_ALL_PATTERN$
|
|
59481
|
+
const CATCH_ALL_PATTERN$5 = "*";
|
|
58556
59482
|
/** Letta Code's `Bash(prefix:*)` prefix-match suffix. */
|
|
58557
59483
|
const PREFIX_SUFFIX = ":*";
|
|
58558
59484
|
const CATEGORY_TO_LETTACODE_TOOL = {
|
|
@@ -58645,7 +59571,7 @@ function categoryOfRule$1(rule) {
|
|
|
58645
59571
|
* widening it would grant commands the source never allowed.
|
|
58646
59572
|
*/
|
|
58647
59573
|
function toLettacodeBashPayload({ pattern, action, logger }) {
|
|
58648
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
59574
|
+
if (pattern === CATCH_ALL_PATTERN$5) return "";
|
|
58649
59575
|
const firstStar = pattern.indexOf("*");
|
|
58650
59576
|
if (firstStar === -1) return pattern;
|
|
58651
59577
|
if (pattern.endsWith(PREFIX_SUFFIX) && firstStar === pattern.length - 1) return pattern;
|
|
@@ -58666,7 +59592,7 @@ function toLettacodeRule({ category, pattern, action, logger }) {
|
|
|
58666
59592
|
pattern,
|
|
58667
59593
|
action,
|
|
58668
59594
|
logger
|
|
58669
|
-
}) : pattern === CATCH_ALL_PATTERN$
|
|
59595
|
+
}) : pattern === CATCH_ALL_PATTERN$5 ? "" : pattern;
|
|
58670
59596
|
if (payload === null) return null;
|
|
58671
59597
|
return payload === "" ? tool : `${tool}(${payload})`;
|
|
58672
59598
|
}
|
|
@@ -58677,7 +59603,7 @@ function fromLettacodeRule(rule) {
|
|
|
58677
59603
|
const { payload } = splitLettacodeRule(rule);
|
|
58678
59604
|
if (payload === void 0) return {
|
|
58679
59605
|
category,
|
|
58680
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59606
|
+
pattern: CATCH_ALL_PATTERN$5
|
|
58681
59607
|
};
|
|
58682
59608
|
const trimmed = payload.trim();
|
|
58683
59609
|
if (trimmed === "") return null;
|
|
@@ -58685,7 +59611,7 @@ function fromLettacodeRule(rule) {
|
|
|
58685
59611
|
const prefix = trimmed.slice(0, -2).trimEnd();
|
|
58686
59612
|
return {
|
|
58687
59613
|
category,
|
|
58688
|
-
pattern: prefix === "" ? CATCH_ALL_PATTERN$
|
|
59614
|
+
pattern: prefix === "" ? CATCH_ALL_PATTERN$5 : `${prefix} *`
|
|
58689
59615
|
};
|
|
58690
59616
|
}
|
|
58691
59617
|
return {
|
|
@@ -59141,6 +60067,377 @@ var MimocodePermissions = class extends OpencodePermissions {
|
|
|
59141
60067
|
static layout = MIMOCODE_LAYOUT;
|
|
59142
60068
|
};
|
|
59143
60069
|
//#endregion
|
|
60070
|
+
//#region src/features/permissions/omp-permissions.ts
|
|
60071
|
+
const CATCH_ALL_PATTERN$4 = "*";
|
|
60072
|
+
const ACTION_TO_OMP_APPROVAL = {
|
|
60073
|
+
allow: "allow",
|
|
60074
|
+
ask: "prompt",
|
|
60075
|
+
deny: "deny"
|
|
60076
|
+
};
|
|
60077
|
+
const OMP_APPROVAL_TO_ACTION = {
|
|
60078
|
+
allow: "allow",
|
|
60079
|
+
prompt: "ask",
|
|
60080
|
+
deny: "deny"
|
|
60081
|
+
};
|
|
60082
|
+
const CATEGORY_TO_OMP_TOOL = {
|
|
60083
|
+
websearch: "web_search",
|
|
60084
|
+
agent: "task"
|
|
60085
|
+
};
|
|
60086
|
+
const OMP_TOOL_TO_CATEGORY = Object.fromEntries(Object.entries(CATEGORY_TO_OMP_TOOL).map(([category, tool]) => [tool, category]));
|
|
60087
|
+
const UNSUPPORTED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
60088
|
+
"webfetch",
|
|
60089
|
+
"notebookedit",
|
|
60090
|
+
"mcp"
|
|
60091
|
+
]);
|
|
60092
|
+
const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
60093
|
+
"bash",
|
|
60094
|
+
"read",
|
|
60095
|
+
"edit",
|
|
60096
|
+
"write",
|
|
60097
|
+
"grep",
|
|
60098
|
+
"glob",
|
|
60099
|
+
"web_search",
|
|
60100
|
+
"task"
|
|
60101
|
+
]);
|
|
60102
|
+
/**
|
|
60103
|
+
* Permissions adapter for oh-my-pi (`omp`).
|
|
60104
|
+
*
|
|
60105
|
+
* oh-my-pi reads approval policy from `config.yml`: `<cwd>/.omp/config.yml` in
|
|
60106
|
+
* project scope and `~/.omp/agent/config.yml` (default profile) globally.
|
|
60107
|
+
*
|
|
60108
|
+
* - `tools.approval.<tool>: allow | prompt | deny` is a per-tool policy honored
|
|
60109
|
+
* in every approval mode. A category's catch-all `*` rule maps here
|
|
60110
|
+
* (`ask` → `prompt`). oh-my-pi has no per-path matcher, so a non-bash category
|
|
60111
|
+
* with pattern-specific rules collapses to one action (deny > ask > allow, with
|
|
60112
|
+
* an implicit `ask` when there is no catch-all) and a warning.
|
|
60113
|
+
* - `bash.patterns: [{ match, approval }]` is an ordered, first-match-wins list
|
|
60114
|
+
* of command globs (only `*` is a wildcard). Every `bash` rule except a
|
|
60115
|
+
* catch-all `allow` is written there, ordered deny → prompt → allow so that
|
|
60116
|
+
* the first match agrees with rulesync's deny > ask > allow precedence. A
|
|
60117
|
+
* catch-all `allow` goes to `tools.approval.bash` instead, because by default
|
|
60118
|
+
* (without `bash.allowCompoundCommands`) an `allow` pattern never approves a
|
|
60119
|
+
* compound command line while the tool policy does — unless the category has
|
|
60120
|
+
* an `ask` rule, which makes `tools.approval.bash` `prompt` (see
|
|
60121
|
+
* `convertBashRules`) and the catch-all `allow` a trailing pattern. oh-my-pi
|
|
60122
|
+
* expands no `?` or `[...]`, so a deny or ask pattern widens them to `*`.
|
|
60123
|
+
* - The all-tools `*` category has no key of its own: its rules restrict bash
|
|
60124
|
+
* commands, and its deny / ask also restrict every other tool's policy (a
|
|
60125
|
+
* catch-all one at its own strictness, a pattern-specific one as `prompt`).
|
|
60126
|
+
*
|
|
60127
|
+
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
60128
|
+
* (the keys rulesync manages) and `bash.patterns` are rewritten; sibling keys
|
|
60129
|
+
* such as `tools.approvalMode` and `bash.enabled` are kept, and the file is
|
|
60130
|
+
* never deleted.
|
|
60131
|
+
*
|
|
60132
|
+
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/settings.md
|
|
60133
|
+
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
60134
|
+
*/
|
|
60135
|
+
var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
60136
|
+
constructor(params) {
|
|
60137
|
+
super({
|
|
60138
|
+
...params,
|
|
60139
|
+
fileContent: params.fileContent ?? ""
|
|
60140
|
+
});
|
|
60141
|
+
}
|
|
60142
|
+
/** `config.yml` holds unrelated user settings, so it must not be deleted. */
|
|
60143
|
+
isDeletable() {
|
|
60144
|
+
return false;
|
|
60145
|
+
}
|
|
60146
|
+
static getSettablePaths({ global = false } = {}) {
|
|
60147
|
+
return {
|
|
60148
|
+
relativeDirPath: global ? OMP_GLOBAL_DIR : OMP_DIR,
|
|
60149
|
+
relativeFilePath: OMP_CONFIG_FILE_NAME
|
|
60150
|
+
};
|
|
60151
|
+
}
|
|
60152
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
60153
|
+
const paths = OmpPermissions.getSettablePaths({ global });
|
|
60154
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
60155
|
+
return new OmpPermissions({
|
|
60156
|
+
outputRoot,
|
|
60157
|
+
relativeDirPath: paths.relativeDirPath,
|
|
60158
|
+
relativeFilePath: paths.relativeFilePath,
|
|
60159
|
+
fileContent,
|
|
60160
|
+
validate,
|
|
60161
|
+
global
|
|
60162
|
+
});
|
|
60163
|
+
}
|
|
60164
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
60165
|
+
const paths = OmpPermissions.getSettablePaths({ global });
|
|
60166
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
60167
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
60168
|
+
const existing = parseSharedConfig({
|
|
60169
|
+
format: "yaml",
|
|
60170
|
+
fileContent: existingContent,
|
|
60171
|
+
filePath
|
|
60172
|
+
});
|
|
60173
|
+
const { approval, patterns, floor } = convertRulesyncToOmp({
|
|
60174
|
+
config: rulesyncPermissions.getJson(),
|
|
60175
|
+
logger
|
|
60176
|
+
});
|
|
60177
|
+
const existingTools = isRecord$1(existing.tools) ? existing.tools : {};
|
|
60178
|
+
const nextApproval = {
|
|
60179
|
+
...isRecord$1(existingTools.approval) ? Object.fromEntries(Object.entries(existingTools.approval).filter(([tool]) => !MANAGED_OMP_TOOLS.has(tool) && !Object.hasOwn(approval, tool)).map(([tool, value]) => [tool, raisedApproval(value, floor)])) : {},
|
|
60180
|
+
...approval
|
|
60181
|
+
};
|
|
60182
|
+
const toolsSiblings = withoutKey(existingTools, "approval");
|
|
60183
|
+
const nextTools = Object.keys(nextApproval).length > 0 ? {
|
|
60184
|
+
...toolsSiblings,
|
|
60185
|
+
approval: nextApproval
|
|
60186
|
+
} : toolsSiblings;
|
|
60187
|
+
const bashSiblings = withoutKey(isRecord$1(existing.bash) ? existing.bash : {}, "patterns");
|
|
60188
|
+
const nextBash = patterns.length > 0 ? {
|
|
60189
|
+
...bashSiblings,
|
|
60190
|
+
patterns
|
|
60191
|
+
} : bashSiblings;
|
|
60192
|
+
return new OmpPermissions({
|
|
60193
|
+
outputRoot,
|
|
60194
|
+
relativeDirPath: paths.relativeDirPath,
|
|
60195
|
+
relativeFilePath: paths.relativeFilePath,
|
|
60196
|
+
fileContent: applySharedConfigPatch({
|
|
60197
|
+
fileKey: sharedConfigFileKey(paths),
|
|
60198
|
+
feature: "permissions",
|
|
60199
|
+
existingContent,
|
|
60200
|
+
patch: {
|
|
60201
|
+
tools: Object.keys(nextTools).length > 0 ? nextTools : void 0,
|
|
60202
|
+
bash: Object.keys(nextBash).length > 0 ? nextBash : void 0
|
|
60203
|
+
},
|
|
60204
|
+
filePath,
|
|
60205
|
+
logger
|
|
60206
|
+
}),
|
|
60207
|
+
validate: true,
|
|
60208
|
+
global
|
|
60209
|
+
});
|
|
60210
|
+
}
|
|
60211
|
+
toRulesyncPermissions() {
|
|
60212
|
+
let config;
|
|
60213
|
+
try {
|
|
60214
|
+
config = parseSharedConfig({
|
|
60215
|
+
format: "yaml",
|
|
60216
|
+
fileContent: this.getFileContent()
|
|
60217
|
+
});
|
|
60218
|
+
} catch (error) {
|
|
60219
|
+
throw new Error(`Failed to parse oh-my-pi config in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
60220
|
+
}
|
|
60221
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(convertOmpToRulesync(config), null, 2) });
|
|
60222
|
+
}
|
|
60223
|
+
validate() {
|
|
60224
|
+
return {
|
|
60225
|
+
success: true,
|
|
60226
|
+
error: null
|
|
60227
|
+
};
|
|
60228
|
+
}
|
|
60229
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
60230
|
+
return new OmpPermissions({
|
|
60231
|
+
outputRoot,
|
|
60232
|
+
relativeDirPath,
|
|
60233
|
+
relativeFilePath,
|
|
60234
|
+
fileContent: "",
|
|
60235
|
+
validate: false,
|
|
60236
|
+
global
|
|
60237
|
+
});
|
|
60238
|
+
}
|
|
60239
|
+
};
|
|
60240
|
+
function withoutKey(record, key) {
|
|
60241
|
+
return Object.fromEntries(Object.entries(record).filter(([name]) => name !== key));
|
|
60242
|
+
}
|
|
60243
|
+
function renamed(map, name) {
|
|
60244
|
+
return Object.hasOwn(map, name) ? map[name] ?? name : name;
|
|
60245
|
+
}
|
|
60246
|
+
function stricter(a, b) {
|
|
60247
|
+
return a !== void 0 && PERMISSION_ACTION_PRIORITY[a] >= PERMISSION_ACTION_PRIORITY[b] ? a : b;
|
|
60248
|
+
}
|
|
60249
|
+
/**
|
|
60250
|
+
* The floor the all-tools `*` category puts under every tool's single policy:
|
|
60251
|
+
* a catch-all deny / ask at its own strictness, and a pattern-specific one as
|
|
60252
|
+
* `ask`, since some input of each tool may match it and a per-tool policy
|
|
60253
|
+
* cannot single that input out. `undefined` when `*` restricts nothing.
|
|
60254
|
+
*/
|
|
60255
|
+
function allToolsFloor(rules) {
|
|
60256
|
+
let floor;
|
|
60257
|
+
let patternSpecific = false;
|
|
60258
|
+
for (const [pattern, action] of Object.entries(rules ?? {})) {
|
|
60259
|
+
if (action === "allow" || isPrototypePollutionKey(pattern)) continue;
|
|
60260
|
+
if (pattern === CATCH_ALL_PATTERN$4) floor = stricter(floor, action);
|
|
60261
|
+
else {
|
|
60262
|
+
patternSpecific = true;
|
|
60263
|
+
floor = stricter(floor, "ask");
|
|
60264
|
+
}
|
|
60265
|
+
}
|
|
60266
|
+
return {
|
|
60267
|
+
floor,
|
|
60268
|
+
patternSpecific
|
|
60269
|
+
};
|
|
60270
|
+
}
|
|
60271
|
+
/**
|
|
60272
|
+
* oh-my-pi expands only `*`, so a `?` or `[...]` in a deny / ask pattern widens
|
|
60273
|
+
* to `*` (a literal match would restrict almost nothing). An allow pattern is
|
|
60274
|
+
* left as is: matched literally it approves less, never more.
|
|
60275
|
+
*/
|
|
60276
|
+
function widenRestrictionPattern(pattern) {
|
|
60277
|
+
return pattern.replace(/\[!?\]?[^\]]*\]|\[|\?/gu, "*").replace(/\*{2,}/gu, "*");
|
|
60278
|
+
}
|
|
60279
|
+
/**
|
|
60280
|
+
* A `bash` category as `bash.patterns` entries (unsorted), plus the
|
|
60281
|
+
* `tools.approval.bash` policy it needs.
|
|
60282
|
+
*
|
|
60283
|
+
* oh-my-pi replaces a `prompt` (or `allow`) pattern match on a critical command
|
|
60284
|
+
* such as `rm -rf /` with an override that carries no policy, which yolo mode
|
|
60285
|
+
* then resolves by `tools.approval.bash` alone. So any `ask` rule sets the tool
|
|
60286
|
+
* policy to `prompt`, and a catch-all `allow` then becomes a trailing
|
|
60287
|
+
* `match: "*"` allow pattern (patterns beat the tool policy) instead of an
|
|
60288
|
+
* `allow` policy that would wave such a command through.
|
|
60289
|
+
*/
|
|
60290
|
+
function convertBashRules$1({ rules, logger }) {
|
|
60291
|
+
const patterns = [];
|
|
60292
|
+
const entries = Object.entries(rules).filter(([pattern]) => !isPrototypePollutionKey(pattern));
|
|
60293
|
+
const hasAsk = entries.some(([, action]) => action === "ask");
|
|
60294
|
+
let policy = hasAsk ? "ask" : void 0;
|
|
60295
|
+
if (hasAsk && rules[CATCH_ALL_PATTERN$4] !== "ask") logger?.warn("oh-my-pi lets a critical command (such as `rm -rf /`) skip a bash prompt pattern unless the bash tool policy prompts, so `tools.approval.bash` was set to \"prompt\": a command no bash allow pattern matches now prompts in every approval mode.");
|
|
60296
|
+
for (const [pattern, action] of entries) {
|
|
60297
|
+
if (pattern === CATCH_ALL_PATTERN$4 && action === "allow") {
|
|
60298
|
+
if (hasAsk) patterns.push({
|
|
60299
|
+
match: CATCH_ALL_PATTERN$4,
|
|
60300
|
+
approval: "allow"
|
|
60301
|
+
});
|
|
60302
|
+
else policy = "allow";
|
|
60303
|
+
continue;
|
|
60304
|
+
}
|
|
60305
|
+
let match = pattern;
|
|
60306
|
+
if (/[?[]/u.test(pattern)) {
|
|
60307
|
+
if (action === "allow") logger?.warn(`oh-my-pi matches only "*" as a wildcard, so "?" and "[" in the bash allow pattern "${pattern}" are matched literally.`);
|
|
60308
|
+
else {
|
|
60309
|
+
match = widenRestrictionPattern(pattern);
|
|
60310
|
+
logger?.warn(`oh-my-pi matches only "*" as a wildcard, so the bash ${action} pattern "${pattern}" was widened to "${match}".`);
|
|
60311
|
+
}
|
|
60312
|
+
}
|
|
60313
|
+
patterns.push({
|
|
60314
|
+
match,
|
|
60315
|
+
approval: ACTION_TO_OMP_APPROVAL[action]
|
|
60316
|
+
});
|
|
60317
|
+
}
|
|
60318
|
+
return {
|
|
60319
|
+
patterns,
|
|
60320
|
+
policy
|
|
60321
|
+
};
|
|
60322
|
+
}
|
|
60323
|
+
/** Raise every non-bash tool policy to the all-tools floor (bash is handled by patterns). */
|
|
60324
|
+
function applyAllToolsFloor({ actions, allTools, logger }) {
|
|
60325
|
+
const { floor } = allTools;
|
|
60326
|
+
if (floor === void 0) return;
|
|
60327
|
+
if (allTools.patternSpecific) logger?.warn(`oh-my-pi sets one approval policy per tool, so the pattern-specific "*" category restrictions were written as "${ACTION_TO_OMP_APPROVAL[floor]}" for every non-bash tool.`);
|
|
60328
|
+
for (const tool of /* @__PURE__ */ new Set([...MANAGED_OMP_TOOLS, ...Object.keys(actions)])) if (tool !== "bash") actions[tool] = stricter(actions[tool], floor);
|
|
60329
|
+
}
|
|
60330
|
+
function convertRulesyncToOmp({ config, logger }) {
|
|
60331
|
+
const actions = {};
|
|
60332
|
+
const patterns = [];
|
|
60333
|
+
const permission = config.permission;
|
|
60334
|
+
const allTools = allToolsFloor(permission["*"]);
|
|
60335
|
+
const categories = permission["*"] === void 0 ? permission : {
|
|
60336
|
+
...permission,
|
|
60337
|
+
bash: bashRulesHonoringAllTools(permission)
|
|
60338
|
+
};
|
|
60339
|
+
for (const [category, rules] of Object.entries(categories)) {
|
|
60340
|
+
if (isPrototypePollutionKey(category) || Object.keys(rules).length === 0) continue;
|
|
60341
|
+
if (category === "*") {
|
|
60342
|
+
if (Object.values(rules).includes("allow")) logger?.warn("oh-my-pi has no all-tools approval key, so the \"*\" category allow rules were skipped.");
|
|
60343
|
+
continue;
|
|
60344
|
+
}
|
|
60345
|
+
if (UNSUPPORTED_CATEGORIES.has(category)) {
|
|
60346
|
+
logger?.warn(`oh-my-pi has no "${category}" tool to set an approval policy on, so its rules were skipped` + (category === "webfetch" ? " (oh-my-pi fetches web pages through `read`)." : "."));
|
|
60347
|
+
continue;
|
|
60348
|
+
}
|
|
60349
|
+
if (category === "bash") {
|
|
60350
|
+
const bash = convertBashRules$1({
|
|
60351
|
+
rules,
|
|
60352
|
+
logger
|
|
60353
|
+
});
|
|
60354
|
+
patterns.push(...bash.patterns);
|
|
60355
|
+
if (bash.policy !== void 0) actions.bash = bash.policy;
|
|
60356
|
+
continue;
|
|
60357
|
+
}
|
|
60358
|
+
const action = collapseRulesToSingleAction({ rules });
|
|
60359
|
+
if (action === void 0) continue;
|
|
60360
|
+
if (hasPatternSpecificRules(rules)) logger?.warn(`oh-my-pi sets one approval policy per tool, so the pattern-specific "${category}" rules were collapsed to "${ACTION_TO_OMP_APPROVAL[action]}" (deny > ask > allow).`);
|
|
60361
|
+
const tool = renamed(CATEGORY_TO_OMP_TOOL, category);
|
|
60362
|
+
if (/^mcp__.+__/u.test(tool)) logger?.warn(`oh-my-pi registers MCP tools as "mcp__<server>_<tool>", so "${tool}" may never match; write oh-my-pi's registered name under a tool-scoped \`omp.permission\` block.`);
|
|
60363
|
+
actions[tool] = stricter(actions[tool], action);
|
|
60364
|
+
}
|
|
60365
|
+
applyAllToolsFloor({
|
|
60366
|
+
actions,
|
|
60367
|
+
allTools,
|
|
60368
|
+
logger
|
|
60369
|
+
});
|
|
60370
|
+
const order = (pattern) => -PERMISSION_ACTION_PRIORITY[OMP_APPROVAL_TO_ACTION[pattern.approval]];
|
|
60371
|
+
patterns.sort((a, b) => order(a) - order(b));
|
|
60372
|
+
return {
|
|
60373
|
+
approval: Object.fromEntries(Object.entries(actions).map(([tool, action]) => [tool, ACTION_TO_OMP_APPROVAL[action]])),
|
|
60374
|
+
patterns,
|
|
60375
|
+
floor: allTools.floor
|
|
60376
|
+
};
|
|
60377
|
+
}
|
|
60378
|
+
/** An existing `tools.approval` value raised to the all-tools floor, if any. */
|
|
60379
|
+
function raisedApproval(value, floor) {
|
|
60380
|
+
if (floor === void 0) return value;
|
|
60381
|
+
const approval = toOmpApproval(value);
|
|
60382
|
+
const current = approval === void 0 ? void 0 : OMP_APPROVAL_TO_ACTION[approval];
|
|
60383
|
+
return ACTION_TO_OMP_APPROVAL[stricter(current, floor)];
|
|
60384
|
+
}
|
|
60385
|
+
function toOmpApproval(value) {
|
|
60386
|
+
if (typeof value !== "string") return;
|
|
60387
|
+
const normalized = value.trim().toLowerCase();
|
|
60388
|
+
return Object.hasOwn(OMP_APPROVAL_TO_ACTION, normalized) ? normalized : void 0;
|
|
60389
|
+
}
|
|
60390
|
+
/**
|
|
60391
|
+
* Fold `tools.approval.bash` (`undefined` when unset) into the imported `bash`
|
|
60392
|
+
* rules. A user `deny` is absolute. Otherwise a `*` pattern beats the tool
|
|
60393
|
+
* policy, which then reaches only compound and critical command lines — so a
|
|
60394
|
+
* `*` allow pattern imports as `allow` only behind an explicit `allow` policy
|
|
60395
|
+
* or in the shape rulesync writes (a `prompt` policy next to `ask` patterns).
|
|
60396
|
+
* Any other `*` allow pattern still prompts for compound lines and imports as
|
|
60397
|
+
* `ask`, so a regenerate cannot widen it into an `allow` policy. A `prompt`
|
|
60398
|
+
* policy with no `*` pattern but an `ask` pattern is what rulesync writes for
|
|
60399
|
+
* ask rules (see `convertBashRules`), so it adds no catch-all.
|
|
60400
|
+
*/
|
|
60401
|
+
function importBashPolicy(bashRules, action) {
|
|
60402
|
+
const catchAll = bashRules[CATCH_ALL_PATTERN$4];
|
|
60403
|
+
const hasAskPattern = Object.values(bashRules).includes("ask");
|
|
60404
|
+
if (action === "deny") bashRules[CATCH_ALL_PATTERN$4] = "deny";
|
|
60405
|
+
else if (catchAll === void 0) {
|
|
60406
|
+
if (action !== void 0 && !(action === "ask" && hasAskPattern)) bashRules[CATCH_ALL_PATTERN$4] = action;
|
|
60407
|
+
} else if (catchAll === "allow" && action !== "allow" && !(action === "ask" && hasAskPattern)) bashRules[CATCH_ALL_PATTERN$4] = "ask";
|
|
60408
|
+
}
|
|
60409
|
+
/** `bash.patterns` as canonical `bash` rules, in first-match order. */
|
|
60410
|
+
function importBashPatterns(value) {
|
|
60411
|
+
const bashRules = {};
|
|
60412
|
+
if (!Array.isArray(value)) return bashRules;
|
|
60413
|
+
for (const entry of value) {
|
|
60414
|
+
if (!isRecord$1(entry) || typeof entry.match !== "string") continue;
|
|
60415
|
+
const match = entry.match.trim().replace(/\s+/gu, " ");
|
|
60416
|
+
const approval = toOmpApproval(entry.approval);
|
|
60417
|
+
if (match === "" || approval === void 0 || Object.hasOwn(bashRules, match)) continue;
|
|
60418
|
+
if (isPrototypePollutionKey(match)) continue;
|
|
60419
|
+
bashRules[match] = OMP_APPROVAL_TO_ACTION[approval];
|
|
60420
|
+
}
|
|
60421
|
+
return bashRules;
|
|
60422
|
+
}
|
|
60423
|
+
function convertOmpToRulesync(config) {
|
|
60424
|
+
const permission = {};
|
|
60425
|
+
const tools = isRecord$1(config.tools) ? config.tools : {};
|
|
60426
|
+
const bashRules = importBashPatterns((isRecord$1(config.bash) ? config.bash : {}).patterns);
|
|
60427
|
+
const bashApproval = toOmpApproval((isRecord$1(tools.approval) ? tools.approval : {}).bash);
|
|
60428
|
+
importBashPolicy(bashRules, bashApproval === void 0 ? void 0 : OMP_APPROVAL_TO_ACTION[bashApproval]);
|
|
60429
|
+
if (isRecord$1(tools.approval)) for (const [tool, value] of Object.entries(tools.approval)) {
|
|
60430
|
+
const approval = toOmpApproval(value);
|
|
60431
|
+
if (approval === void 0 || tool.trim() === "" || isPrototypePollutionKey(tool)) continue;
|
|
60432
|
+
const action = OMP_APPROVAL_TO_ACTION[approval];
|
|
60433
|
+
if (tool === "bash") continue;
|
|
60434
|
+
const category = renamed(OMP_TOOL_TO_CATEGORY, tool);
|
|
60435
|
+
permission[category] = { [CATCH_ALL_PATTERN$4]: stricter(permission[category]?.[CATCH_ALL_PATTERN$4], action) };
|
|
60436
|
+
}
|
|
60437
|
+
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
60438
|
+
return { permission };
|
|
60439
|
+
}
|
|
60440
|
+
//#endregion
|
|
59144
60441
|
//#region src/features/permissions/pi-permissions.ts
|
|
59145
60442
|
/** The one `settings.json` key this feature owns. */
|
|
59146
60443
|
const DEFAULT_TOOLS_KEY = "defaultTools";
|
|
@@ -60168,7 +61465,9 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
60168
61465
|
"eager",
|
|
60169
61466
|
"listDirectory",
|
|
60170
61467
|
"todoWrite",
|
|
60171
|
-
"workflowsEnabled"
|
|
61468
|
+
"workflowsEnabled",
|
|
61469
|
+
"workflowNameOnly",
|
|
61470
|
+
"executionSandbox"
|
|
60172
61471
|
];
|
|
60173
61472
|
const QWEN_OVERRIDE_SECURITY_KEYS = [
|
|
60174
61473
|
"folderTrust",
|
|
@@ -60237,6 +61536,16 @@ const QWEN_OVERRIDE_GROUPS = [{
|
|
|
60237
61536
|
overrideKeys: QWEN_OVERRIDE_TOOLS_KEYS,
|
|
60238
61537
|
scopedKeys: {
|
|
60239
61538
|
workflowsEnabled: { rule: "workspace-stripped" },
|
|
61539
|
+
executionSandbox: {
|
|
61540
|
+
rule: "workspace-stripped",
|
|
61541
|
+
grants: () => true,
|
|
61542
|
+
globalNote: "Qwen Code ignores this key in workspace settings so a repository cannot choose its own sandbox; in the global scope it sets the filesystem and network policy every tool command runs under for every project on this machine (a System value still replaces it, and Qwen Code needs a restart to pick it up)."
|
|
61543
|
+
},
|
|
61544
|
+
workflowNameOnly: {
|
|
61545
|
+
rule: "global-machine-wide",
|
|
61546
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. Qwen Code lets a workspace only turn this key on: \`true\` restricts the model to named workflows in this repository, while any other value cannot lift a restriction set in user or system settings.`,
|
|
61547
|
+
globalNote: "Qwen Code honors this key in the global scope for every project on this machine, and a repository can only add the restriction, never lift it, so this decides whether the model may run inline or path-based workflow scripts anywhere a workspace does not turn the restriction on itself."
|
|
61548
|
+
},
|
|
60240
61549
|
approvalMode: {
|
|
60241
61550
|
rule: "global-machine-wide",
|
|
60242
61551
|
projectNote: autonomyProjectNote
|
|
@@ -66055,6 +67364,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
66055
67364
|
supportsImport: true
|
|
66056
67365
|
}
|
|
66057
67366
|
}],
|
|
67367
|
+
["codewhale", {
|
|
67368
|
+
class: CodewhalePermissions,
|
|
67369
|
+
meta: {
|
|
67370
|
+
supportsProject: false,
|
|
67371
|
+
supportsGlobal: true,
|
|
67372
|
+
supportsImport: true
|
|
67373
|
+
}
|
|
67374
|
+
}],
|
|
66058
67375
|
["codexcli", {
|
|
66059
67376
|
class: CodexcliPermissions,
|
|
66060
67377
|
meta: {
|
|
@@ -66223,6 +67540,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
66223
67540
|
supportsImport: true
|
|
66224
67541
|
}
|
|
66225
67542
|
}],
|
|
67543
|
+
["omp", {
|
|
67544
|
+
class: OmpPermissions,
|
|
67545
|
+
meta: {
|
|
67546
|
+
supportsProject: true,
|
|
67547
|
+
supportsGlobal: true,
|
|
67548
|
+
supportsImport: true
|
|
67549
|
+
}
|
|
67550
|
+
}],
|
|
66226
67551
|
["opencode", {
|
|
66227
67552
|
class: OpencodePermissions,
|
|
66228
67553
|
meta: {
|
|
@@ -67102,6 +68427,10 @@ var DirFeatureProcessor = class extends RulesyncSourceConsumer {
|
|
|
67102
68427
|
continue;
|
|
67103
68428
|
}
|
|
67104
68429
|
try {
|
|
68430
|
+
await assertWritablePathInsideRoot({
|
|
68431
|
+
rootPath: this.outputRoot,
|
|
68432
|
+
targetPath: root
|
|
68433
|
+
});
|
|
67105
68434
|
await assertWritablePathInsideRoot({
|
|
67106
68435
|
rootPath: this.outputRoot,
|
|
67107
68436
|
targetPath: dirPath
|
|
@@ -67133,7 +68462,7 @@ var DirFeatureProcessor = class extends RulesyncSourceConsumer {
|
|
|
67133
68462
|
this.logger.warn(`Refusing to delete ${quoteForLog(filePath)}: this run wrote a file whose path differs from it only in case, which on a case-insensitive filesystem is the very file it wrote`);
|
|
67134
68463
|
continue;
|
|
67135
68464
|
}
|
|
67136
|
-
if (isClaimed(filePath)) continue;
|
|
68465
|
+
if (await isClaimed(filePath)) continue;
|
|
67137
68466
|
orphanPaths.add(filePath);
|
|
67138
68467
|
}
|
|
67139
68468
|
}
|
|
@@ -75689,16 +77018,30 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
75689
77018
|
* path and vetted as writable inside this run's output root. Shared by the
|
|
75690
77019
|
* two halves of the orphan sweep so both look in exactly the same places,
|
|
75691
77020
|
* under exactly the same guard.
|
|
77021
|
+
*
|
|
77022
|
+
* A root that fails the guard — a symbolic link, even one that stays inside
|
|
77023
|
+
* the output root, such as a dotfiles checkout linked from the home
|
|
77024
|
+
* directory — is left out with a warning rather than failing the run. The
|
|
77025
|
+
* write path already wrote through such a link, and the sweep runs after
|
|
77026
|
+
* those writes land, so throwing here would report a failure for a run whose
|
|
77027
|
+
* output is in place. Sweeping through the link is not an option either: it
|
|
77028
|
+
* can lead to a directory rulesync does not manage, and its entries would be
|
|
77029
|
+
* deleted as orphans.
|
|
75692
77030
|
*/
|
|
75693
77031
|
async loadExistingSkillsRoots(paths) {
|
|
75694
77032
|
const existingRoots = [];
|
|
75695
77033
|
for (const root of toolSkillSearchRoots(paths)) {
|
|
75696
77034
|
const skillsDirPath = (0, node_path.join)(this.outputRoot, root);
|
|
75697
77035
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
75698
|
-
|
|
75699
|
-
|
|
75700
|
-
|
|
75701
|
-
|
|
77036
|
+
try {
|
|
77037
|
+
await assertWritablePathInsideRoot({
|
|
77038
|
+
rootPath: this.outputRoot,
|
|
77039
|
+
targetPath: skillsDirPath
|
|
77040
|
+
});
|
|
77041
|
+
} catch (error) {
|
|
77042
|
+
this.logger.warn(`Skipping the orphan sweep for ${quoteForLog(skillsDirPath)}; nothing under it is deleted: ${stripControlCharacters(formatError(error))}`);
|
|
77043
|
+
continue;
|
|
77044
|
+
}
|
|
75702
77045
|
existingRoots.push({
|
|
75703
77046
|
root,
|
|
75704
77047
|
skillsDirPath
|
|
@@ -76805,6 +78148,288 @@ var AugmentcodePluginSubagent = class extends AugmentcodeSubagent {
|
|
|
76805
78148
|
}
|
|
76806
78149
|
};
|
|
76807
78150
|
//#endregion
|
|
78151
|
+
//#region src/features/subagents/roo-subagent.ts
|
|
78152
|
+
/**
|
|
78153
|
+
* Default tool groups assigned to a generated mode when the rulesync source
|
|
78154
|
+
* does not specify a `roo.groups` override. Mirrors Roo's built-in code mode
|
|
78155
|
+
* (full read/edit/command/mcp access; browser is opt-in).
|
|
78156
|
+
*/
|
|
78157
|
+
const DEFAULT_GROUPS = [
|
|
78158
|
+
"read",
|
|
78159
|
+
"edit",
|
|
78160
|
+
"command",
|
|
78161
|
+
"mcp"
|
|
78162
|
+
];
|
|
78163
|
+
/**
|
|
78164
|
+
* One entry of a mode's `groups` array. Either a bare toolset name
|
|
78165
|
+
* (`"read"`, `"edit"`, `"command"`, `"mcp"`, `"browser"`) or a tuple pairing a
|
|
78166
|
+
* toolset (in practice `"edit"`) with a file restriction object.
|
|
78167
|
+
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
78168
|
+
*/
|
|
78169
|
+
const RooModeGroupSchema = zod_mini.z.union([zod_mini.z.string(), zod_mini.z.tuple([zod_mini.z.string(), zod_mini.z.looseObject({})])]);
|
|
78170
|
+
/**
|
|
78171
|
+
* A single custom mode inside the `customModes` array of `.roomodes`.
|
|
78172
|
+
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
78173
|
+
*/
|
|
78174
|
+
const RooModeSchema = zod_mini.z.looseObject({
|
|
78175
|
+
slug: zod_mini.z.string(),
|
|
78176
|
+
name: zod_mini.z.string(),
|
|
78177
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
78178
|
+
roleDefinition: zod_mini.z.string(),
|
|
78179
|
+
whenToUse: zod_mini.z.optional(zod_mini.z.string()),
|
|
78180
|
+
groups: zod_mini.z.optional(zod_mini.z.array(RooModeGroupSchema)),
|
|
78181
|
+
customInstructions: zod_mini.z.optional(zod_mini.z.string())
|
|
78182
|
+
});
|
|
78183
|
+
const RooModesFileSchema = zod_mini.z.looseObject({ customModes: zod_mini.z.array(RooModeSchema) });
|
|
78184
|
+
/**
|
|
78185
|
+
* Sanitize a string into a valid Roo mode slug (`^[a-zA-Z0-9-]+$`): lowercase,
|
|
78186
|
+
* non-alphanumeric runs collapsed to a single hyphen, leading/trailing hyphens
|
|
78187
|
+
* trimmed. Falls back to `"mode"` if nothing usable remains.
|
|
78188
|
+
*/
|
|
78189
|
+
function sanitizeRooSlug(raw) {
|
|
78190
|
+
const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
78191
|
+
return slug.length > 0 ? slug : "mode";
|
|
78192
|
+
}
|
|
78193
|
+
function stringifyRooModes(modes) {
|
|
78194
|
+
return (0, js_yaml.dump)({ customModes: modes }, {
|
|
78195
|
+
lineWidth: -1,
|
|
78196
|
+
noRefs: true
|
|
78197
|
+
});
|
|
78198
|
+
}
|
|
78199
|
+
/**
|
|
78200
|
+
* Roo Code custom-modes adapter.
|
|
78201
|
+
*
|
|
78202
|
+
* Roo reads project-level custom modes from a single aggregated `.roomodes`
|
|
78203
|
+
* file at the workspace root (`docs.roocode.com` 301-redirects to the docs
|
|
78204
|
+
* below). Each rulesync subagent becomes one entry in the `customModes` array.
|
|
78205
|
+
* Because the native format aggregates N subagents into ONE file, every
|
|
78206
|
+
* `RooSubagent` instance carries the FULL `customModes` array and targets the
|
|
78207
|
+
* same `.roomodes` path; generation builds the aggregate once via
|
|
78208
|
+
* {@link RooSubagent.fromRulesyncSubagents}.
|
|
78209
|
+
*
|
|
78210
|
+
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
78211
|
+
*/
|
|
78212
|
+
var RooSubagent = class extends ToolSubagent {
|
|
78213
|
+
/** Name of the aggregated custom-modes file inside `getSettablePaths().relativeDirPath`. */
|
|
78214
|
+
static modesFileName = ROO_MODES_FILE_NAME;
|
|
78215
|
+
/** Frontmatter section of a rulesync subagent that carries the mode fields. */
|
|
78216
|
+
static modeSectionKey = "roo";
|
|
78217
|
+
/** Tool groups a generated mode gets when its section sets no `groups`. */
|
|
78218
|
+
static defaultGroups = DEFAULT_GROUPS;
|
|
78219
|
+
/** Tool target written into the frontmatter of imported subagents. */
|
|
78220
|
+
static importTarget = "roo";
|
|
78221
|
+
modes;
|
|
78222
|
+
constructor({ modes, ...rest }) {
|
|
78223
|
+
if (rest.validate !== false) {
|
|
78224
|
+
const result = RooModesFileSchema.safeParse({ customModes: modes });
|
|
78225
|
+
if (!result.success) throw new Error(`Invalid custom modes in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
78226
|
+
}
|
|
78227
|
+
super({
|
|
78228
|
+
...rest,
|
|
78229
|
+
fileContent: stringifyRooModes(modes)
|
|
78230
|
+
});
|
|
78231
|
+
this.modes = modes;
|
|
78232
|
+
}
|
|
78233
|
+
static getSettablePaths(_options = {}) {
|
|
78234
|
+
return { relativeDirPath: "." };
|
|
78235
|
+
}
|
|
78236
|
+
getModes() {
|
|
78237
|
+
return this.modes;
|
|
78238
|
+
}
|
|
78239
|
+
getBody() {
|
|
78240
|
+
return this.fileContent;
|
|
78241
|
+
}
|
|
78242
|
+
/**
|
|
78243
|
+
* Map a single rulesync subagent to a Roo custom mode. The body becomes
|
|
78244
|
+
* `roleDefinition`; the optional `roo:` frontmatter section (the
|
|
78245
|
+
* {@link RooSubagent.modeSectionKey} section) supplies
|
|
78246
|
+
* `groups`, `whenToUse`, `customInstructions`, an explicit `slug`, and may
|
|
78247
|
+
* override `roleDefinition`. rulesync `name`/`description` are preferred,
|
|
78248
|
+
* while tool-specific `roo:` values take precedence where defined.
|
|
78249
|
+
*/
|
|
78250
|
+
static toRooMode(rulesyncSubagent) {
|
|
78251
|
+
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
78252
|
+
const rawSection = frontmatter[this.modeSectionKey] ?? {};
|
|
78253
|
+
const slug = typeof rawSection.slug === "string" && rawSection.slug.length > 0 ? sanitizeRooSlug(rawSection.slug) : sanitizeRooSlug((0, node_path.basename)(rulesyncSubagent.getRelativeFilePath()).replace(/\.[^.]+$/, ""));
|
|
78254
|
+
const roleDefinition = typeof rawSection.roleDefinition === "string" ? rawSection.roleDefinition : rulesyncSubagent.getBody();
|
|
78255
|
+
const groups = Array.isArray(rawSection.groups) ? rawSection.groups : [...this.defaultGroups];
|
|
78256
|
+
const mode = {
|
|
78257
|
+
slug,
|
|
78258
|
+
name: frontmatter.name,
|
|
78259
|
+
roleDefinition,
|
|
78260
|
+
groups
|
|
78261
|
+
};
|
|
78262
|
+
if (frontmatter.description) mode.description = frontmatter.description;
|
|
78263
|
+
if (typeof rawSection.whenToUse === "string") mode.whenToUse = rawSection.whenToUse;
|
|
78264
|
+
if (typeof rawSection.customInstructions === "string") mode.customInstructions = rawSection.customInstructions;
|
|
78265
|
+
return mode;
|
|
78266
|
+
}
|
|
78267
|
+
/**
|
|
78268
|
+
* Aggregate every targeted rulesync subagent into a single `.roomodes` file.
|
|
78269
|
+
* Modes are de-duplicated by `slug` (last one wins) to keep the array unique.
|
|
78270
|
+
*/
|
|
78271
|
+
static fromRulesyncSubagents({ outputRoot = process.cwd(), rulesyncSubagents, validate = true }) {
|
|
78272
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
78273
|
+
for (const rulesyncSubagent of rulesyncSubagents) {
|
|
78274
|
+
const mode = this.toRooMode(rulesyncSubagent);
|
|
78275
|
+
bySlug.set(mode.slug, mode);
|
|
78276
|
+
}
|
|
78277
|
+
return new this({
|
|
78278
|
+
outputRoot,
|
|
78279
|
+
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
78280
|
+
relativeFilePath: this.modesFileName,
|
|
78281
|
+
modes: [...bySlug.values()],
|
|
78282
|
+
validate
|
|
78283
|
+
});
|
|
78284
|
+
}
|
|
78285
|
+
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true }) {
|
|
78286
|
+
return this.fromRulesyncSubagents({
|
|
78287
|
+
outputRoot,
|
|
78288
|
+
rulesyncSubagents: [rulesyncSubagent],
|
|
78289
|
+
validate
|
|
78290
|
+
});
|
|
78291
|
+
}
|
|
78292
|
+
/**
|
|
78293
|
+
* Convert every custom mode in the aggregated `.roomodes` file back into an
|
|
78294
|
+
* individual rulesync subagent. The processor calls this in the import
|
|
78295
|
+
* direction (one `.roomodes` tool file fans out to N rulesync files).
|
|
78296
|
+
*/
|
|
78297
|
+
toRulesyncSubagents() {
|
|
78298
|
+
const { modeSectionKey, importTarget } = this.constructor;
|
|
78299
|
+
return this.modes.map((mode) => {
|
|
78300
|
+
const { slug, name, description, roleDefinition, whenToUse, groups, customInstructions, ...rest } = mode;
|
|
78301
|
+
const modeSection = {
|
|
78302
|
+
...rest,
|
|
78303
|
+
slug,
|
|
78304
|
+
...groups ? { groups } : {},
|
|
78305
|
+
...whenToUse !== void 0 ? { whenToUse } : {},
|
|
78306
|
+
...customInstructions !== void 0 ? { customInstructions } : {}
|
|
78307
|
+
};
|
|
78308
|
+
return new RulesyncSubagent({
|
|
78309
|
+
outputRoot: ".",
|
|
78310
|
+
frontmatter: {
|
|
78311
|
+
targets: [importTarget],
|
|
78312
|
+
name,
|
|
78313
|
+
...description !== void 0 ? { description } : {},
|
|
78314
|
+
[modeSectionKey]: modeSection
|
|
78315
|
+
},
|
|
78316
|
+
body: roleDefinition,
|
|
78317
|
+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
78318
|
+
relativeFilePath: `${sanitizeRooSlug(slug)}.md`,
|
|
78319
|
+
validate: true
|
|
78320
|
+
});
|
|
78321
|
+
});
|
|
78322
|
+
}
|
|
78323
|
+
toRulesyncSubagent() {
|
|
78324
|
+
const first = this.toRulesyncSubagents()[0];
|
|
78325
|
+
if (!first) throw new Error(`No custom modes found in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)} to convert.`);
|
|
78326
|
+
return first;
|
|
78327
|
+
}
|
|
78328
|
+
validate() {
|
|
78329
|
+
const result = RooModesFileSchema.safeParse({ customModes: this.modes });
|
|
78330
|
+
if (result.success) return {
|
|
78331
|
+
success: true,
|
|
78332
|
+
error: null
|
|
78333
|
+
};
|
|
78334
|
+
return {
|
|
78335
|
+
success: false,
|
|
78336
|
+
error: /* @__PURE__ */ new Error(`Invalid custom modes in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
78337
|
+
};
|
|
78338
|
+
}
|
|
78339
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
78340
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
78341
|
+
rulesyncSubagent,
|
|
78342
|
+
toolTarget: "roo"
|
|
78343
|
+
});
|
|
78344
|
+
}
|
|
78345
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
78346
|
+
const paths = this.getSettablePaths();
|
|
78347
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
78348
|
+
const fileContent = await readFileContent(filePath);
|
|
78349
|
+
let parsed;
|
|
78350
|
+
try {
|
|
78351
|
+
parsed = loadYaml(fileContent);
|
|
78352
|
+
} catch (error) {
|
|
78353
|
+
throw new Error(`Failed to parse custom modes (${filePath}): ${formatError(error)}`, { cause: error });
|
|
78354
|
+
}
|
|
78355
|
+
const result = RooModesFileSchema.safeParse(parsed ?? { customModes: [] });
|
|
78356
|
+
if (!result.success) throw new Error(`Invalid custom modes in ${filePath}: ${formatError(result.error)}`);
|
|
78357
|
+
return new this({
|
|
78358
|
+
outputRoot,
|
|
78359
|
+
relativeDirPath: paths.relativeDirPath,
|
|
78360
|
+
relativeFilePath: (0, node_path.basename)(relativeFilePath),
|
|
78361
|
+
modes: result.data.customModes,
|
|
78362
|
+
validate
|
|
78363
|
+
});
|
|
78364
|
+
}
|
|
78365
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
78366
|
+
return new this({
|
|
78367
|
+
outputRoot,
|
|
78368
|
+
relativeDirPath,
|
|
78369
|
+
relativeFilePath,
|
|
78370
|
+
modes: [],
|
|
78371
|
+
validate: false
|
|
78372
|
+
});
|
|
78373
|
+
}
|
|
78374
|
+
};
|
|
78375
|
+
//#endregion
|
|
78376
|
+
//#region src/features/subagents/bob-subagent.ts
|
|
78377
|
+
/**
|
|
78378
|
+
* Subagent (custom-mode) generator for **IBM Bob** (Bob IDE and Bob Shell).
|
|
78379
|
+
*
|
|
78380
|
+
* Bob has no file-based subagents — its native subagents are the two fixed
|
|
78381
|
+
* presets `explore` and `general` — but it reads custom modes from an
|
|
78382
|
+
* aggregated `customModes` YAML file in Roo's format, so every targeted rulesync
|
|
78383
|
+
* subagent becomes one mode in `.bob/custom_modes.yaml`, exactly as the `roo`
|
|
78384
|
+
* target fills `.roomodes`. Only the project file is emitted: the Bob IDE and
|
|
78385
|
+
* Bob Shell docs name different global files.
|
|
78386
|
+
*
|
|
78387
|
+
* The mode fields ride a `bob:` frontmatter section rather than Roo's `roo:`
|
|
78388
|
+
* one, because Bob's tool groups are spelled differently (`execute` where Roo
|
|
78389
|
+
* says `command`, plus `skill`, `workflow`, `todo`, `subtask`, `subagent` and
|
|
78390
|
+
* `mode`), so a `roo.groups` list is not valid for Bob. A mode without
|
|
78391
|
+
* `bob.groups` gets `["read", "edit", "execute", "mcp"]`, Roo's default with
|
|
78392
|
+
* Bob IDE's spelling — the one Bob's shared approval settings use too. The Bob
|
|
78393
|
+
* Shell custom-modes page still lists Roo's `command` / `browser` groups, so a
|
|
78394
|
+
* mode meant for the Shell should spell its groups out in `bob.groups`.
|
|
78395
|
+
* `bob.allowedSubagents` (Bob IDE) is emitted when it is a string list, and
|
|
78396
|
+
* every other mode field is lifted back into `bob:` on import.
|
|
78397
|
+
*
|
|
78398
|
+
* @see https://bob.ibm.com/docs/ide/configuration/custom-modes
|
|
78399
|
+
* @see https://bob.ibm.com/docs/shell/configuration/custom-modes-bobshell
|
|
78400
|
+
* @see https://bob.ibm.com/docs/ide/features/subagents
|
|
78401
|
+
*/
|
|
78402
|
+
var BobSubagent = class extends RooSubagent {
|
|
78403
|
+
static modesFileName = BOB_CUSTOM_MODES_FILE_NAME;
|
|
78404
|
+
static modeSectionKey = "bob";
|
|
78405
|
+
static defaultGroups = [
|
|
78406
|
+
"read",
|
|
78407
|
+
"edit",
|
|
78408
|
+
"execute",
|
|
78409
|
+
"mcp"
|
|
78410
|
+
];
|
|
78411
|
+
static importTarget = "bob";
|
|
78412
|
+
static getSettablePaths(_options = {}) {
|
|
78413
|
+
return {
|
|
78414
|
+
relativeDirPath: BOB_DIR,
|
|
78415
|
+
relativeFilePath: BOB_CUSTOM_MODES_FILE_NAME
|
|
78416
|
+
};
|
|
78417
|
+
}
|
|
78418
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
78419
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
78420
|
+
rulesyncSubagent,
|
|
78421
|
+
toolTarget: "bob"
|
|
78422
|
+
});
|
|
78423
|
+
}
|
|
78424
|
+
static toRooMode(rulesyncSubagent) {
|
|
78425
|
+
const mode = super.toRooMode(rulesyncSubagent);
|
|
78426
|
+
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
78427
|
+
const bobSection = isRecord$1(frontmatter.bob) ? frontmatter.bob : {};
|
|
78428
|
+
if (isStringArray$2(bobSection.allowedSubagents)) mode.allowedSubagents = bobSection.allowedSubagents;
|
|
78429
|
+
return mode;
|
|
78430
|
+
}
|
|
78431
|
+
};
|
|
78432
|
+
//#endregion
|
|
76808
78433
|
//#region src/features/subagents/claudecode-subagent.ts
|
|
76809
78434
|
const ClaudecodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
76810
78435
|
name: zod_mini.z.string(),
|
|
@@ -78552,15 +80177,14 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
78552
80177
|
toRulesyncSubagent() {
|
|
78553
80178
|
const { name, description, ...restFields } = this.frontmatter;
|
|
78554
80179
|
const devinSection = { ...restFields };
|
|
78555
|
-
const rulesyncFrontmatter = {
|
|
78556
|
-
targets: ["*"],
|
|
78557
|
-
name,
|
|
78558
|
-
description,
|
|
78559
|
-
...Object.keys(devinSection).length > 0 && { devin: devinSection }
|
|
78560
|
-
};
|
|
78561
80180
|
return new RulesyncSubagent({
|
|
78562
|
-
outputRoot:
|
|
78563
|
-
frontmatter:
|
|
80181
|
+
outputRoot: ".",
|
|
80182
|
+
frontmatter: {
|
|
80183
|
+
targets: ["*"],
|
|
80184
|
+
name,
|
|
80185
|
+
description,
|
|
80186
|
+
...Object.keys(devinSection).length > 0 && { devin: devinSection }
|
|
80187
|
+
},
|
|
78564
80188
|
body: this.body,
|
|
78565
80189
|
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
78566
80190
|
relativeFilePath: `${this.getSubagentName()}.md`,
|
|
@@ -81329,221 +82953,6 @@ var ReasonixSubagent = class ReasonixSubagent extends ToolSubagent {
|
|
|
81329
82953
|
}
|
|
81330
82954
|
};
|
|
81331
82955
|
//#endregion
|
|
81332
|
-
//#region src/features/subagents/roo-subagent.ts
|
|
81333
|
-
/**
|
|
81334
|
-
* Default tool groups assigned to a generated mode when the rulesync source
|
|
81335
|
-
* does not specify a `roo.groups` override. Mirrors Roo's built-in code mode
|
|
81336
|
-
* (full read/edit/command/mcp access; browser is opt-in).
|
|
81337
|
-
*/
|
|
81338
|
-
const DEFAULT_GROUPS = [
|
|
81339
|
-
"read",
|
|
81340
|
-
"edit",
|
|
81341
|
-
"command",
|
|
81342
|
-
"mcp"
|
|
81343
|
-
];
|
|
81344
|
-
/**
|
|
81345
|
-
* One entry of a mode's `groups` array. Either a bare toolset name
|
|
81346
|
-
* (`"read"`, `"edit"`, `"command"`, `"mcp"`, `"browser"`) or a tuple pairing a
|
|
81347
|
-
* toolset (in practice `"edit"`) with a file restriction object.
|
|
81348
|
-
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
81349
|
-
*/
|
|
81350
|
-
const RooModeGroupSchema = zod_mini.z.union([zod_mini.z.string(), zod_mini.z.tuple([zod_mini.z.string(), zod_mini.z.looseObject({})])]);
|
|
81351
|
-
/**
|
|
81352
|
-
* A single custom mode inside the `customModes` array of `.roomodes`.
|
|
81353
|
-
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
81354
|
-
*/
|
|
81355
|
-
const RooModeSchema = zod_mini.z.looseObject({
|
|
81356
|
-
slug: zod_mini.z.string(),
|
|
81357
|
-
name: zod_mini.z.string(),
|
|
81358
|
-
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
81359
|
-
roleDefinition: zod_mini.z.string(),
|
|
81360
|
-
whenToUse: zod_mini.z.optional(zod_mini.z.string()),
|
|
81361
|
-
groups: zod_mini.z.optional(zod_mini.z.array(RooModeGroupSchema)),
|
|
81362
|
-
customInstructions: zod_mini.z.optional(zod_mini.z.string())
|
|
81363
|
-
});
|
|
81364
|
-
const RooModesFileSchema = zod_mini.z.looseObject({ customModes: zod_mini.z.array(RooModeSchema) });
|
|
81365
|
-
/**
|
|
81366
|
-
* Sanitize a string into a valid Roo mode slug (`^[a-zA-Z0-9-]+$`): lowercase,
|
|
81367
|
-
* non-alphanumeric runs collapsed to a single hyphen, leading/trailing hyphens
|
|
81368
|
-
* trimmed. Falls back to `"mode"` if nothing usable remains.
|
|
81369
|
-
*/
|
|
81370
|
-
function sanitizeRooSlug(raw) {
|
|
81371
|
-
const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
81372
|
-
return slug.length > 0 ? slug : "mode";
|
|
81373
|
-
}
|
|
81374
|
-
function stringifyRooModes(modes) {
|
|
81375
|
-
return (0, js_yaml.dump)({ customModes: modes }, {
|
|
81376
|
-
lineWidth: -1,
|
|
81377
|
-
noRefs: true
|
|
81378
|
-
});
|
|
81379
|
-
}
|
|
81380
|
-
/**
|
|
81381
|
-
* Roo Code custom-modes adapter.
|
|
81382
|
-
*
|
|
81383
|
-
* Roo reads project-level custom modes from a single aggregated `.roomodes`
|
|
81384
|
-
* file at the workspace root (`docs.roocode.com` 301-redirects to the docs
|
|
81385
|
-
* below). Each rulesync subagent becomes one entry in the `customModes` array.
|
|
81386
|
-
* Because the native format aggregates N subagents into ONE file, every
|
|
81387
|
-
* `RooSubagent` instance carries the FULL `customModes` array and targets the
|
|
81388
|
-
* same `.roomodes` path; generation builds the aggregate once via
|
|
81389
|
-
* {@link RooSubagent.fromRulesyncSubagents}.
|
|
81390
|
-
*
|
|
81391
|
-
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
81392
|
-
*/
|
|
81393
|
-
var RooSubagent = class extends ToolSubagent {
|
|
81394
|
-
modes;
|
|
81395
|
-
constructor({ modes, ...rest }) {
|
|
81396
|
-
if (rest.validate !== false) {
|
|
81397
|
-
const result = RooModesFileSchema.safeParse({ customModes: modes });
|
|
81398
|
-
if (!result.success) throw new Error(`Invalid .roomodes in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
81399
|
-
}
|
|
81400
|
-
super({
|
|
81401
|
-
...rest,
|
|
81402
|
-
fileContent: stringifyRooModes(modes)
|
|
81403
|
-
});
|
|
81404
|
-
this.modes = modes;
|
|
81405
|
-
}
|
|
81406
|
-
static getSettablePaths(_options = {}) {
|
|
81407
|
-
return { relativeDirPath: "." };
|
|
81408
|
-
}
|
|
81409
|
-
getModes() {
|
|
81410
|
-
return this.modes;
|
|
81411
|
-
}
|
|
81412
|
-
getBody() {
|
|
81413
|
-
return this.fileContent;
|
|
81414
|
-
}
|
|
81415
|
-
/**
|
|
81416
|
-
* Map a single rulesync subagent to a Roo custom mode. The body becomes
|
|
81417
|
-
* `roleDefinition`; the optional `roo:` frontmatter section supplies
|
|
81418
|
-
* `groups`, `whenToUse`, `customInstructions`, an explicit `slug`, and may
|
|
81419
|
-
* override `roleDefinition`. rulesync `name`/`description` are preferred,
|
|
81420
|
-
* while tool-specific `roo:` values take precedence where defined.
|
|
81421
|
-
*/
|
|
81422
|
-
static toRooMode(rulesyncSubagent) {
|
|
81423
|
-
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
81424
|
-
const rawSection = frontmatter.roo ?? {};
|
|
81425
|
-
const slug = typeof rawSection.slug === "string" && rawSection.slug.length > 0 ? sanitizeRooSlug(rawSection.slug) : sanitizeRooSlug((0, node_path.basename)(rulesyncSubagent.getRelativeFilePath()).replace(/\.[^.]+$/, ""));
|
|
81426
|
-
const roleDefinition = typeof rawSection.roleDefinition === "string" ? rawSection.roleDefinition : rulesyncSubagent.getBody();
|
|
81427
|
-
const groups = Array.isArray(rawSection.groups) ? rawSection.groups : [...DEFAULT_GROUPS];
|
|
81428
|
-
const mode = {
|
|
81429
|
-
slug,
|
|
81430
|
-
name: frontmatter.name,
|
|
81431
|
-
roleDefinition,
|
|
81432
|
-
groups
|
|
81433
|
-
};
|
|
81434
|
-
if (frontmatter.description) mode.description = frontmatter.description;
|
|
81435
|
-
if (typeof rawSection.whenToUse === "string") mode.whenToUse = rawSection.whenToUse;
|
|
81436
|
-
if (typeof rawSection.customInstructions === "string") mode.customInstructions = rawSection.customInstructions;
|
|
81437
|
-
return mode;
|
|
81438
|
-
}
|
|
81439
|
-
/**
|
|
81440
|
-
* Aggregate every targeted rulesync subagent into a single `.roomodes` file.
|
|
81441
|
-
* Modes are de-duplicated by `slug` (last one wins) to keep the array unique.
|
|
81442
|
-
*/
|
|
81443
|
-
static fromRulesyncSubagents({ outputRoot = process.cwd(), rulesyncSubagents, validate = true }) {
|
|
81444
|
-
const bySlug = /* @__PURE__ */ new Map();
|
|
81445
|
-
for (const rulesyncSubagent of rulesyncSubagents) {
|
|
81446
|
-
const mode = this.toRooMode(rulesyncSubagent);
|
|
81447
|
-
bySlug.set(mode.slug, mode);
|
|
81448
|
-
}
|
|
81449
|
-
return new this({
|
|
81450
|
-
outputRoot,
|
|
81451
|
-
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
81452
|
-
relativeFilePath: ROO_MODES_FILE_NAME,
|
|
81453
|
-
modes: [...bySlug.values()],
|
|
81454
|
-
validate
|
|
81455
|
-
});
|
|
81456
|
-
}
|
|
81457
|
-
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true }) {
|
|
81458
|
-
return this.fromRulesyncSubagents({
|
|
81459
|
-
outputRoot,
|
|
81460
|
-
rulesyncSubagents: [rulesyncSubagent],
|
|
81461
|
-
validate
|
|
81462
|
-
});
|
|
81463
|
-
}
|
|
81464
|
-
/**
|
|
81465
|
-
* Convert every custom mode in the aggregated `.roomodes` file back into an
|
|
81466
|
-
* individual rulesync subagent. The processor calls this in the import
|
|
81467
|
-
* direction (one `.roomodes` tool file fans out to N rulesync files).
|
|
81468
|
-
*/
|
|
81469
|
-
toRulesyncSubagents() {
|
|
81470
|
-
return this.modes.map((mode) => {
|
|
81471
|
-
const { slug, name, description, roleDefinition, whenToUse, groups, customInstructions, ...rest } = mode;
|
|
81472
|
-
const rooSection = {
|
|
81473
|
-
...rest,
|
|
81474
|
-
slug,
|
|
81475
|
-
...groups ? { groups } : {},
|
|
81476
|
-
...whenToUse !== void 0 ? { whenToUse } : {},
|
|
81477
|
-
...customInstructions !== void 0 ? { customInstructions } : {}
|
|
81478
|
-
};
|
|
81479
|
-
return new RulesyncSubagent({
|
|
81480
|
-
outputRoot: ".",
|
|
81481
|
-
frontmatter: {
|
|
81482
|
-
targets: ["roo"],
|
|
81483
|
-
name,
|
|
81484
|
-
...description !== void 0 ? { description } : {},
|
|
81485
|
-
roo: rooSection
|
|
81486
|
-
},
|
|
81487
|
-
body: roleDefinition,
|
|
81488
|
-
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
81489
|
-
relativeFilePath: `${sanitizeRooSlug(slug)}.md`,
|
|
81490
|
-
validate: true
|
|
81491
|
-
});
|
|
81492
|
-
});
|
|
81493
|
-
}
|
|
81494
|
-
toRulesyncSubagent() {
|
|
81495
|
-
const first = this.toRulesyncSubagents()[0];
|
|
81496
|
-
if (!first) throw new Error("No custom modes found in .roomodes to convert.");
|
|
81497
|
-
return first;
|
|
81498
|
-
}
|
|
81499
|
-
validate() {
|
|
81500
|
-
const result = RooModesFileSchema.safeParse({ customModes: this.modes });
|
|
81501
|
-
if (result.success) return {
|
|
81502
|
-
success: true,
|
|
81503
|
-
error: null
|
|
81504
|
-
};
|
|
81505
|
-
return {
|
|
81506
|
-
success: false,
|
|
81507
|
-
error: /* @__PURE__ */ new Error(`Invalid .roomodes in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
81508
|
-
};
|
|
81509
|
-
}
|
|
81510
|
-
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
81511
|
-
return this.isTargetedByRulesyncSubagentDefault({
|
|
81512
|
-
rulesyncSubagent,
|
|
81513
|
-
toolTarget: "roo"
|
|
81514
|
-
});
|
|
81515
|
-
}
|
|
81516
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
81517
|
-
const paths = this.getSettablePaths();
|
|
81518
|
-
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
81519
|
-
const fileContent = await readFileContent(filePath);
|
|
81520
|
-
let parsed;
|
|
81521
|
-
try {
|
|
81522
|
-
parsed = loadYaml(fileContent);
|
|
81523
|
-
} catch (error) {
|
|
81524
|
-
throw new Error(`Failed to parse .roomodes (${filePath}): ${formatError(error)}`, { cause: error });
|
|
81525
|
-
}
|
|
81526
|
-
const result = RooModesFileSchema.safeParse(parsed ?? { customModes: [] });
|
|
81527
|
-
if (!result.success) throw new Error(`Invalid .roomodes in ${filePath}: ${formatError(result.error)}`);
|
|
81528
|
-
return new this({
|
|
81529
|
-
outputRoot,
|
|
81530
|
-
relativeDirPath: paths.relativeDirPath,
|
|
81531
|
-
relativeFilePath: (0, node_path.basename)(relativeFilePath),
|
|
81532
|
-
modes: result.data.customModes,
|
|
81533
|
-
validate
|
|
81534
|
-
});
|
|
81535
|
-
}
|
|
81536
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
81537
|
-
return new this({
|
|
81538
|
-
outputRoot,
|
|
81539
|
-
relativeDirPath,
|
|
81540
|
-
relativeFilePath,
|
|
81541
|
-
modes: [],
|
|
81542
|
-
validate: false
|
|
81543
|
-
});
|
|
81544
|
-
}
|
|
81545
|
-
};
|
|
81546
|
-
//#endregion
|
|
81547
82956
|
//#region src/features/subagents/tabnine-subagent.ts
|
|
81548
82957
|
const TabnineSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
81549
82958
|
name: zod_mini.z.string(),
|
|
@@ -82041,20 +83450,19 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
82041
83450
|
const resolvedBody = this.resolvedSystemPrompt ?? system_prompt;
|
|
82042
83451
|
const unresolvedPromptId = this.resolvedSystemPrompt === void 0 ? system_prompt_id : void 0;
|
|
82043
83452
|
const fileStem = (0, node_path.basename)(this.getRelativeFilePath(), ".toml");
|
|
82044
|
-
const rulesyncFrontmatter = {
|
|
82045
|
-
targets: ["vibe"],
|
|
82046
|
-
name: display_name ?? fileStem,
|
|
82047
|
-
...description !== void 0 && { description },
|
|
82048
|
-
vibe: {
|
|
82049
|
-
...display_name !== void 0 && { display_name },
|
|
82050
|
-
...description !== void 0 && { description },
|
|
82051
|
-
...unresolvedPromptId !== void 0 && { system_prompt_id: unresolvedPromptId },
|
|
82052
|
-
...vibeSection
|
|
82053
|
-
}
|
|
82054
|
-
};
|
|
82055
83453
|
return new RulesyncSubagent({
|
|
82056
|
-
outputRoot:
|
|
82057
|
-
frontmatter:
|
|
83454
|
+
outputRoot: ".",
|
|
83455
|
+
frontmatter: {
|
|
83456
|
+
targets: ["vibe"],
|
|
83457
|
+
name: display_name ?? fileStem,
|
|
83458
|
+
...description !== void 0 && { description },
|
|
83459
|
+
vibe: {
|
|
83460
|
+
...display_name !== void 0 && { display_name },
|
|
83461
|
+
...description !== void 0 && { description },
|
|
83462
|
+
...unresolvedPromptId !== void 0 && { system_prompt_id: unresolvedPromptId },
|
|
83463
|
+
...vibeSection
|
|
83464
|
+
}
|
|
83465
|
+
},
|
|
82058
83466
|
body: resolvedBody ?? "",
|
|
82059
83467
|
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
82060
83468
|
relativeFilePath: this.getRelativeFilePath().replace(/\.toml$/, ".md"),
|
|
@@ -82487,6 +83895,15 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
82487
83895
|
filePattern: "*.md"
|
|
82488
83896
|
}
|
|
82489
83897
|
}],
|
|
83898
|
+
["bob", {
|
|
83899
|
+
class: BobSubagent,
|
|
83900
|
+
meta: {
|
|
83901
|
+
supportsProject: true,
|
|
83902
|
+
supportsSimulated: false,
|
|
83903
|
+
supportsGlobal: false,
|
|
83904
|
+
filePattern: "custom_modes.yaml"
|
|
83905
|
+
}
|
|
83906
|
+
}],
|
|
82490
83907
|
["claudecode", {
|
|
82491
83908
|
class: ClaudecodeSubagent,
|
|
82492
83909
|
meta: {
|
|
@@ -83109,10 +84526,15 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
83109
84526
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
83110
84527
|
const dirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
83111
84528
|
const baseDir = (0, node_path.join)(rootOutputRoot, dirPath);
|
|
83112
|
-
if (forDeletion && await directoryExists(baseDir))
|
|
83113
|
-
|
|
83114
|
-
|
|
83115
|
-
|
|
84529
|
+
if (forDeletion && await directoryExists(baseDir)) try {
|
|
84530
|
+
await assertWritablePathInsideRoot({
|
|
84531
|
+
rootPath: rootOutputRoot,
|
|
84532
|
+
targetPath: baseDir
|
|
84533
|
+
});
|
|
84534
|
+
} catch (error) {
|
|
84535
|
+
this.logger.warn(`Skipping the orphan sweep for ${quoteForLog(baseDir)}; nothing under it is deleted: ${stripControlCharacters(formatError(error))}`);
|
|
84536
|
+
continue;
|
|
84537
|
+
}
|
|
83116
84538
|
const subagentFilePaths = (await findFilesByGlobs(factory.meta.filePattern, {
|
|
83117
84539
|
cwd: baseDir,
|
|
83118
84540
|
followSymbolicLinks: !forDeletion
|
|
@@ -84074,6 +85496,16 @@ const AntigravityRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
84074
85496
|
description: zod_mini.z.optional(zod_mini.z.string())
|
|
84075
85497
|
});
|
|
84076
85498
|
/**
|
|
85499
|
+
* File name of a non-root rule in the shared global `~/.gemini/config/rules/`
|
|
85500
|
+
* directory. Antigravity reads only its top level, so a nested rulesync rule
|
|
85501
|
+
* (`frontend/style.md`) is flattened into a single file name
|
|
85502
|
+
* (`frontend-style.md`). The IDE and the CLI both use it so they write the
|
|
85503
|
+
* same file for the same rule.
|
|
85504
|
+
*/
|
|
85505
|
+
function toGlobalRuleFileName(relativeFilePath) {
|
|
85506
|
+
return relativeFilePath.split(/[\\/]/).join("-");
|
|
85507
|
+
}
|
|
85508
|
+
/**
|
|
84077
85509
|
* Converts a comma-separated globs string or array to an array of globs.
|
|
84078
85510
|
* @param globs - Comma-separated globs string (e.g., "*.ts,*.js") or array of globs
|
|
84079
85511
|
* @returns Array of glob patterns
|
|
@@ -84304,7 +85736,7 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
84304
85736
|
});
|
|
84305
85737
|
return new AntigravityCliRule({
|
|
84306
85738
|
...params,
|
|
84307
|
-
relativeFilePath: params.relativeFilePath
|
|
85739
|
+
relativeFilePath: toGlobalRuleFileName(params.relativeFilePath),
|
|
84308
85740
|
fileContent,
|
|
84309
85741
|
global
|
|
84310
85742
|
});
|
|
@@ -84402,10 +85834,14 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
84402
85834
|
*
|
|
84403
85835
|
* - Project scope: every rule is placed as a non-root file in
|
|
84404
85836
|
* `.agents/rules/` with Antigravity trigger frontmatter.
|
|
84405
|
-
* - Global scope: a
|
|
84406
|
-
*
|
|
85837
|
+
* - Global scope: a plain `~/.gemini/GEMINI.md` root file without
|
|
85838
|
+
* frontmatter, and non-root rules with trigger frontmatter in
|
|
85839
|
+
* `~/.gemini/config/rules/`. Both are shared with the Antigravity CLI, which
|
|
85840
|
+
* writes the same files for the same rules.
|
|
85841
|
+
*
|
|
85842
|
+
* @see https://antigravity.google/docs/rules
|
|
84407
85843
|
*/
|
|
84408
|
-
var AntigravityIdeRule = class
|
|
85844
|
+
var AntigravityIdeRule = class extends ToolRule {
|
|
84409
85845
|
frontmatter;
|
|
84410
85846
|
body;
|
|
84411
85847
|
constructor({ frontmatter, body, ...rest }) {
|
|
@@ -84433,28 +85869,18 @@ var AntigravityIdeRule = class AntigravityIdeRule extends ToolRule {
|
|
|
84433
85869
|
};
|
|
84434
85870
|
}
|
|
84435
85871
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
84436
|
-
if (global) return {
|
|
85872
|
+
if (global) return {
|
|
85873
|
+
root: this.getGlobalRootPath(excludeToolDir),
|
|
85874
|
+
nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_IDE_GEMINI_DIR, ANTIGRAVITY_IDE_GLOBAL_RULES_SUBDIR, excludeToolDir) }
|
|
85875
|
+
};
|
|
84437
85876
|
return {
|
|
84438
85877
|
root: this.getProjectRootPath(),
|
|
84439
85878
|
nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_IDE_AGENTS_DIR, "rules", excludeToolDir) }
|
|
84440
85879
|
};
|
|
84441
85880
|
}
|
|
84442
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
85881
|
+
static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
|
|
84443
85882
|
const paths = this.getSettablePaths({ global });
|
|
84444
|
-
if (
|
|
84445
|
-
const rootPath = paths.root;
|
|
84446
|
-
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootPath.relativeDirPath, rootPath.relativeFilePath));
|
|
84447
|
-
return new this({
|
|
84448
|
-
outputRoot,
|
|
84449
|
-
relativeDirPath: rootPath.relativeDirPath,
|
|
84450
|
-
relativeFilePath: rootPath.relativeFilePath,
|
|
84451
|
-
frontmatter: {},
|
|
84452
|
-
body: fileContent,
|
|
84453
|
-
validate,
|
|
84454
|
-
root: true
|
|
84455
|
-
});
|
|
84456
|
-
}
|
|
84457
|
-
if (relativeFilePath === paths.root.relativeFilePath) {
|
|
85883
|
+
if (relativeFilePath === paths.root.relativeFilePath && (relativeDirPath === void 0 || relativeDirPath === paths.root.relativeDirPath)) {
|
|
84458
85884
|
const rootPath = paths.root;
|
|
84459
85885
|
const rootContent = await readFileContent((0, node_path.join)(outputRoot, rootPath.relativeDirPath, rootPath.relativeFilePath));
|
|
84460
85886
|
return new this({
|
|
@@ -84464,10 +85890,10 @@ var AntigravityIdeRule = class AntigravityIdeRule extends ToolRule {
|
|
|
84464
85890
|
frontmatter: {},
|
|
84465
85891
|
body: rootContent,
|
|
84466
85892
|
validate,
|
|
84467
|
-
root: true
|
|
85893
|
+
root: true,
|
|
85894
|
+
global
|
|
84468
85895
|
});
|
|
84469
85896
|
}
|
|
84470
|
-
if (!("nonRoot" in paths) || !paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
84471
85897
|
const nonRootDirPath = paths.nonRoot.relativeDirPath;
|
|
84472
85898
|
const filePath = (0, node_path.join)(outputRoot, nonRootDirPath, relativeFilePath);
|
|
84473
85899
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
@@ -84484,23 +85910,12 @@ var AntigravityIdeRule = class AntigravityIdeRule extends ToolRule {
|
|
|
84484
85910
|
body,
|
|
84485
85911
|
frontmatter: parsedFrontmatter,
|
|
84486
85912
|
validate,
|
|
84487
|
-
root: false
|
|
85913
|
+
root: false,
|
|
85914
|
+
global
|
|
84488
85915
|
});
|
|
84489
85916
|
}
|
|
84490
85917
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
84491
85918
|
const paths = this.getSettablePaths({ global });
|
|
84492
|
-
if (global) {
|
|
84493
|
-
const rootPath = paths.root;
|
|
84494
|
-
return new this({
|
|
84495
|
-
outputRoot,
|
|
84496
|
-
relativeDirPath: rootPath.relativeDirPath,
|
|
84497
|
-
relativeFilePath: rootPath.relativeFilePath,
|
|
84498
|
-
frontmatter: {},
|
|
84499
|
-
body: rulesyncRule.getBody(),
|
|
84500
|
-
validate,
|
|
84501
|
-
root: true
|
|
84502
|
-
});
|
|
84503
|
-
}
|
|
84504
85919
|
if (rulesyncRule.getFrontmatter().root) {
|
|
84505
85920
|
const rootPath = paths.root;
|
|
84506
85921
|
return new this({
|
|
@@ -84510,7 +85925,8 @@ var AntigravityIdeRule = class AntigravityIdeRule extends ToolRule {
|
|
|
84510
85925
|
frontmatter: {},
|
|
84511
85926
|
body: rulesyncRule.getBody(),
|
|
84512
85927
|
validate,
|
|
84513
|
-
root: true
|
|
85928
|
+
root: true,
|
|
85929
|
+
global
|
|
84514
85930
|
});
|
|
84515
85931
|
}
|
|
84516
85932
|
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
@@ -84520,16 +85936,16 @@ var AntigravityIdeRule = class AntigravityIdeRule extends ToolRule {
|
|
|
84520
85936
|
const strategy = STRATEGIES$1.find((s) => s.canHandle(storedTrigger));
|
|
84521
85937
|
if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
|
|
84522
85938
|
const frontmatter = strategy.generateFrontmatter(normalized, rulesyncFrontmatter);
|
|
84523
|
-
|
|
84524
|
-
const kebabCaseFilename = toKebabCaseFilename(rulesyncRule.getRelativeFilePath());
|
|
85939
|
+
const relativeFilePath = global ? toGlobalRuleFileName(rulesyncRule.getRelativeFilePath()) : toKebabCaseFilename(rulesyncRule.getRelativeFilePath());
|
|
84525
85940
|
return new this({
|
|
84526
85941
|
outputRoot,
|
|
84527
85942
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
84528
|
-
relativeFilePath
|
|
85943
|
+
relativeFilePath,
|
|
84529
85944
|
frontmatter,
|
|
84530
85945
|
body: rulesyncRule.getBody(),
|
|
84531
85946
|
validate,
|
|
84532
|
-
root: false
|
|
85947
|
+
root: false,
|
|
85948
|
+
global
|
|
84533
85949
|
});
|
|
84534
85950
|
}
|
|
84535
85951
|
toRulesyncRule() {
|
|
@@ -84574,15 +85990,26 @@ var AntigravityIdeRule = class AntigravityIdeRule extends ToolRule {
|
|
|
84574
85990
|
error: null
|
|
84575
85991
|
};
|
|
84576
85992
|
}
|
|
85993
|
+
/**
|
|
85994
|
+
* `~/.gemini/config/rules/` is shared with the Antigravity CLI and holds
|
|
85995
|
+
* global rules the user created outside rulesync, which cannot be told apart
|
|
85996
|
+
* from generated ones, so the orphan sweep never removes files there.
|
|
85997
|
+
*/
|
|
85998
|
+
isDeletable() {
|
|
85999
|
+
return !this.global || this.root;
|
|
86000
|
+
}
|
|
84577
86001
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
84578
|
-
|
|
86002
|
+
const rootPath = this.getSettablePaths({ global }).root;
|
|
86003
|
+
const isRoot = relativeFilePath === rootPath.relativeFilePath && relativeDirPath === rootPath.relativeDirPath;
|
|
86004
|
+
return new this({
|
|
84579
86005
|
outputRoot,
|
|
84580
86006
|
relativeDirPath,
|
|
84581
86007
|
relativeFilePath,
|
|
84582
86008
|
frontmatter: {},
|
|
84583
86009
|
body: "",
|
|
84584
86010
|
validate: false,
|
|
84585
|
-
root:
|
|
86011
|
+
root: isRoot,
|
|
86012
|
+
global
|
|
84586
86013
|
});
|
|
84587
86014
|
}
|
|
84588
86015
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
@@ -91263,7 +92690,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
91263
92690
|
meta: {
|
|
91264
92691
|
extension: "md",
|
|
91265
92692
|
supportsGlobal: true,
|
|
91266
|
-
ruleDiscoveryMode: "auto"
|
|
92693
|
+
ruleDiscoveryMode: "auto",
|
|
92694
|
+
sharedGlobalNonRootDir: true
|
|
91267
92695
|
}
|
|
91268
92696
|
}],
|
|
91269
92697
|
["antigravity-plugin", {
|
|
@@ -91900,6 +93328,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
91900
93328
|
toolRules,
|
|
91901
93329
|
factory
|
|
91902
93330
|
});
|
|
93331
|
+
await this.warnForWarpLegacyRootFile(toolRules);
|
|
91903
93332
|
this.warnForInstructionBudget({
|
|
91904
93333
|
toolRules,
|
|
91905
93334
|
meta
|
|
@@ -92313,6 +93742,24 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
92313
93742
|
this.logger.warn(`Writing ${stripControlCharacters(rootFileRelativePath)} for ${this.toolTarget} means ${listedNames.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""} will no longer be read. Run \`rulesync import --targets ${this.toolTarget}\` first to carry that content into ${RULESYNC_RULES_RELATIVE_DIR_PATH}, or delete ${listedNames.length === 1 ? "it" : "them"} once you have checked the content is already in the root file.`);
|
|
92314
93743
|
}
|
|
92315
93744
|
/**
|
|
93745
|
+
* Warp still reads a legacy `WARP.md`, and when both `WARP.md` and
|
|
93746
|
+
* `AGENTS.md` sit in the same directory, `WARP.md` takes priority — so the
|
|
93747
|
+
* `AGENTS.md` this run writes would never be read. Rulesync neither writes
|
|
93748
|
+
* nor deletes `WARP.md` (it is hand-authored), so the only thing it can do is
|
|
93749
|
+
* say so. Project scope only: the documented precedence is about project
|
|
93750
|
+
* rules, and Warp's global rule source is `~/.agents/AGENTS.md`.
|
|
93751
|
+
* @see https://docs.warp.dev/agents/capabilities/rules/
|
|
93752
|
+
*/
|
|
93753
|
+
async warnForWarpLegacyRootFile(toolRules) {
|
|
93754
|
+
if (this.toolTarget !== "warp" || this.global) return;
|
|
93755
|
+
if (toolRules.length === 0) return;
|
|
93756
|
+
const { root } = WarpRule.getSettablePaths();
|
|
93757
|
+
const legacyRelativePath = (0, node_path.join)(root.relativeDirPath, WARP_LEGACY_RULE_FILE_NAME);
|
|
93758
|
+
if (!await fileExists((0, node_path.join)(this.outputRoot, legacyRelativePath))) return;
|
|
93759
|
+
const rootRelativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
93760
|
+
this.logger.warn(`${legacyRelativePath} exists next to ${rootRelativePath}, and Warp reads WARP.md instead of AGENTS.md when both are present, so the generated ${rootRelativePath} is ignored by Warp. Move any content you still need into ${RULESYNC_RULES_RELATIVE_DIR_PATH}, then delete or rename ${legacyRelativePath}.`);
|
|
93761
|
+
}
|
|
93762
|
+
/**
|
|
92316
93763
|
* Handle localRoot rule generation based on tool target.
|
|
92317
93764
|
* - `separate-local-file`: writes a dedicated `*.local.md` root file
|
|
92318
93765
|
* (claudecode/legacy: `./CLAUDE.local.md`, rovodev: `./AGENTS.local.md`)
|
|
@@ -93694,26 +95141,64 @@ function createFoldRootOverwriteWatch({ logger }) {
|
|
|
93694
95141
|
}
|
|
93695
95142
|
//#endregion
|
|
93696
95143
|
//#region src/lib/orphan-sweep.ts
|
|
93697
|
-
function createOrphanSweepPlan() {
|
|
95144
|
+
function createOrphanSweepPlan({ logger } = {}) {
|
|
93698
95145
|
const generatedPaths = /* @__PURE__ */ new Set();
|
|
93699
95146
|
const generatedTrees = /* @__PURE__ */ new Set();
|
|
93700
95147
|
const deferredSweeps = [];
|
|
93701
95148
|
const deletedPathsByFeature = /* @__PURE__ */ new Map();
|
|
93702
|
-
const
|
|
93703
|
-
|
|
95149
|
+
const warnedUnresolvable = /* @__PURE__ */ new Set();
|
|
95150
|
+
let landingClaims;
|
|
95151
|
+
const isInsideTree = ({ path, trees }) => {
|
|
95152
|
+
let current = path;
|
|
93704
95153
|
let parent = (0, node_path.dirname)(current);
|
|
93705
95154
|
while (parent !== current) {
|
|
93706
|
-
if (
|
|
95155
|
+
if (trees.has(parent)) return true;
|
|
93707
95156
|
current = parent;
|
|
93708
95157
|
parent = (0, node_path.dirname)(current);
|
|
93709
95158
|
}
|
|
93710
95159
|
return false;
|
|
93711
95160
|
};
|
|
95161
|
+
const followLinks = async (path) => {
|
|
95162
|
+
try {
|
|
95163
|
+
return await writeLandingPath(path);
|
|
95164
|
+
} catch {
|
|
95165
|
+
return null;
|
|
95166
|
+
}
|
|
95167
|
+
};
|
|
95168
|
+
const landingsOf = async (paths) => {
|
|
95169
|
+
const landings = await Promise.all([...paths].map((path) => followLinks(path)));
|
|
95170
|
+
return new Set(landings.filter((landing) => landing !== null));
|
|
95171
|
+
};
|
|
95172
|
+
const getLandingClaims = () => {
|
|
95173
|
+
landingClaims ??= Promise.all([landingsOf(generatedPaths), landingsOf(generatedTrees)]).then(([paths, trees]) => ({
|
|
95174
|
+
paths,
|
|
95175
|
+
trees: new Set([...trees].filter((tree) => (0, node_path.dirname)(tree) !== tree))
|
|
95176
|
+
}));
|
|
95177
|
+
return landingClaims;
|
|
95178
|
+
};
|
|
95179
|
+
const landsOnClaim = async ({ path, exactly }) => {
|
|
95180
|
+
const resolved = (0, node_path.resolve)(path);
|
|
95181
|
+
const landing = await followLinks(resolved);
|
|
95182
|
+
if (landing === null) {
|
|
95183
|
+
if (!warnedUnresolvable.has(resolved)) {
|
|
95184
|
+
warnedUnresolvable.add(resolved);
|
|
95185
|
+
logger?.warn(`Refusing to sweep ${quoteForLog(resolved)}: its symbolic links cannot be followed (a link cycle or an unreadable directory)`);
|
|
95186
|
+
}
|
|
95187
|
+
return true;
|
|
95188
|
+
}
|
|
95189
|
+
const claims = await getLandingClaims();
|
|
95190
|
+
return claims.paths.has(landing) || !exactly && isInsideTree({
|
|
95191
|
+
path: landing,
|
|
95192
|
+
trees: claims.trees
|
|
95193
|
+
});
|
|
95194
|
+
};
|
|
93712
95195
|
const plan = {
|
|
93713
95196
|
registerGenerated({ paths }) {
|
|
95197
|
+
landingClaims = void 0;
|
|
93714
95198
|
for (const path of paths) generatedPaths.add((0, node_path.resolve)(path));
|
|
93715
95199
|
},
|
|
93716
95200
|
registerGeneratedTree({ paths }) {
|
|
95201
|
+
landingClaims = void 0;
|
|
93717
95202
|
for (const path of paths) {
|
|
93718
95203
|
const resolved = (0, node_path.resolve)(path);
|
|
93719
95204
|
generatedPaths.add(resolved);
|
|
@@ -93722,13 +95207,26 @@ function createOrphanSweepPlan() {
|
|
|
93722
95207
|
},
|
|
93723
95208
|
isGenerated({ path }) {
|
|
93724
95209
|
const resolved = (0, node_path.resolve)(path);
|
|
93725
|
-
return generatedPaths.has(resolved) ||
|
|
95210
|
+
return generatedPaths.has(resolved) || isInsideTree({
|
|
95211
|
+
path: resolved,
|
|
95212
|
+
trees: generatedTrees
|
|
95213
|
+
});
|
|
93726
95214
|
},
|
|
93727
|
-
isGeneratedExactly({ path }) {
|
|
93728
|
-
return generatedPaths.has((0, node_path.resolve)(path))
|
|
95215
|
+
async isGeneratedExactly({ path }) {
|
|
95216
|
+
return generatedPaths.has((0, node_path.resolve)(path)) || await landsOnClaim({
|
|
95217
|
+
path,
|
|
95218
|
+
exactly: true
|
|
95219
|
+
});
|
|
93729
95220
|
},
|
|
93730
|
-
rejectClaimed({ items, getPath }) {
|
|
93731
|
-
|
|
95221
|
+
async rejectClaimed({ items, getPath }) {
|
|
95222
|
+
const claimed = await Promise.all(items.map(async (item) => {
|
|
95223
|
+
const path = getPath(item);
|
|
95224
|
+
return plan.isGenerated({ path }) || await landsOnClaim({
|
|
95225
|
+
path,
|
|
95226
|
+
exactly: false
|
|
95227
|
+
});
|
|
95228
|
+
}));
|
|
95229
|
+
return items.filter((_, index) => !claimed[index]);
|
|
93732
95230
|
},
|
|
93733
95231
|
defer({ sweep, reportDeleted }) {
|
|
93734
95232
|
deferredSweeps.push({
|
|
@@ -93922,19 +95420,19 @@ function scheduleRetiredTargetSweeps({ config, logger, sweepPlan, sourceLoadFail
|
|
|
93922
95420
|
}
|
|
93923
95421
|
}
|
|
93924
95422
|
async function sweepFiles({ processor, sweepPlan, unownedPaths }) {
|
|
93925
|
-
const candidates = sweepPlan.rejectClaimed({
|
|
95423
|
+
const candidates = (await sweepPlan.rejectClaimed({
|
|
93926
95424
|
items: await processor.loadToolFiles({ forDeletion: true }),
|
|
93927
95425
|
getPath: (f) => f.getFilePath()
|
|
93928
|
-
}).filter((f) => !unownedPaths.has((0, node_path.resolve)(f.getFilePath())));
|
|
95426
|
+
})).filter((f) => !unownedPaths.has((0, node_path.resolve)(f.getFilePath())));
|
|
93929
95427
|
const present = await Promise.all(candidates.map((f) => fileExists(f.getFilePath())));
|
|
93930
95428
|
return await processor.removeOrphanAiFiles(candidates.filter((_, index) => present[index]), []) > 0;
|
|
93931
95429
|
}
|
|
93932
95430
|
async function sweepDirs({ processor, sweepPlan }) {
|
|
93933
|
-
return await processor.removeOrphanAiDirs(sweepPlan.rejectClaimed({
|
|
95431
|
+
return await processor.removeOrphanAiDirs(await sweepPlan.rejectClaimed({
|
|
93934
95432
|
items: await processor.loadToolDirsToDelete(),
|
|
93935
95433
|
getPath: (d) => d.getDirPath()
|
|
93936
95434
|
}), []) + await processor.removeOrphanFlatFiles({
|
|
93937
|
-
existingFlatFiles: sweepPlan.rejectClaimed({
|
|
95435
|
+
existingFlatFiles: await sweepPlan.rejectClaimed({
|
|
93938
95436
|
items: await processor.loadToolFlatFilesToDelete(),
|
|
93939
95437
|
getPath: (d) => d.getFlatFilePath() ?? d.getDirPath()
|
|
93940
95438
|
}),
|
|
@@ -94121,7 +95619,7 @@ async function processFeatureGeneration(params) {
|
|
|
94121
95619
|
if (config.getDelete() && !processor.hasRulesyncSourceLoadFailure()) sweepPlan.defer({
|
|
94122
95620
|
sweep: async () => {
|
|
94123
95621
|
const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
|
|
94124
|
-
return await processor.removeOrphanAiFiles(sweepPlan.rejectClaimed({
|
|
95622
|
+
return await processor.removeOrphanAiFiles(await sweepPlan.rejectClaimed({
|
|
94125
95623
|
items: existingToolFiles,
|
|
94126
95624
|
getPath: (f) => f.getFilePath()
|
|
94127
95625
|
}), toolFiles) > 0;
|
|
@@ -94135,6 +95633,16 @@ async function processFeatureGeneration(params) {
|
|
|
94135
95633
|
sourceLoadFailed: processor.hasRulesyncSourceLoadFailure()
|
|
94136
95634
|
};
|
|
94137
95635
|
}
|
|
95636
|
+
/**
|
|
95637
|
+
* Whether a write to `targetPath` lands strictly below `rootPath` once every
|
|
95638
|
+
* link on the way to either is followed. A cycle on either side answers false.
|
|
95639
|
+
*/
|
|
95640
|
+
async function landsStrictlyBelowRoot({ rootPath, targetPath }) {
|
|
95641
|
+
const [rootLanding, targetLanding] = await Promise.all([writeLandingPath(rootPath), writeLandingPath(targetPath)]);
|
|
95642
|
+
if (rootLanding === null || targetLanding === null) return false;
|
|
95643
|
+
const relativePath = (0, node_path.relative)(rootLanding, targetLanding);
|
|
95644
|
+
return relativePath !== "" && !pathEscapesRoot(relativePath);
|
|
95645
|
+
}
|
|
94138
95646
|
async function processDirFeatureGeneration(params) {
|
|
94139
95647
|
const { config, processor, toolDirs, sweepPlan } = params;
|
|
94140
95648
|
let totalCount = 0;
|
|
@@ -94145,7 +95653,11 @@ async function processDirFeatureGeneration(params) {
|
|
|
94145
95653
|
allPaths.push(...writeResult.paths);
|
|
94146
95654
|
if (writeResult.count > 0) hasDiff = true;
|
|
94147
95655
|
const ownedTrees = toolDirs.filter((d) => d.ownsDirTree());
|
|
94148
|
-
|
|
95656
|
+
const treesBelowRoot = await Promise.all(ownedTrees.map((d) => landsStrictlyBelowRoot({
|
|
95657
|
+
rootPath: d.getOutputRoot(),
|
|
95658
|
+
targetPath: d.getDirPath()
|
|
95659
|
+
})));
|
|
95660
|
+
sweepPlan.registerGeneratedTree({ paths: ownedTrees.filter((_, index) => treesBelowRoot[index]).map((d) => d.getDirPath()) });
|
|
94149
95661
|
sweepPlan.registerGenerated({ paths: toolDirs.flatMap((d) => {
|
|
94150
95662
|
const dirPath = d.getDirPath();
|
|
94151
95663
|
const mainFile = d.getMainFile();
|
|
@@ -94158,13 +95670,13 @@ async function processDirFeatureGeneration(params) {
|
|
|
94158
95670
|
if (config.getDelete() && !processor.hasRulesyncSourceLoadFailure()) sweepPlan.defer({
|
|
94159
95671
|
sweep: async () => {
|
|
94160
95672
|
const existingToolDirs = await processor.loadToolDirsToDelete();
|
|
94161
|
-
const orphanDirCount = await processor.removeOrphanAiDirs(sweepPlan.rejectClaimed({
|
|
95673
|
+
const orphanDirCount = await processor.removeOrphanAiDirs(await sweepPlan.rejectClaimed({
|
|
94162
95674
|
items: existingToolDirs,
|
|
94163
95675
|
getPath: (d) => d.getDirPath()
|
|
94164
95676
|
}), toolDirs);
|
|
94165
95677
|
const existingFlatFiles = await processor.loadToolFlatFilesToDelete();
|
|
94166
95678
|
const orphanFileCount = await processor.removeOrphanFlatFiles({
|
|
94167
|
-
existingFlatFiles: sweepPlan.rejectClaimed({
|
|
95679
|
+
existingFlatFiles: await sweepPlan.rejectClaimed({
|
|
94168
95680
|
items: existingFlatFiles,
|
|
94169
95681
|
getPath: (d) => d.getFlatFilePath() ?? d.getDirPath()
|
|
94170
95682
|
}),
|
|
@@ -94191,10 +95703,10 @@ async function processEmptyFeatureGeneration(params) {
|
|
|
94191
95703
|
if (config.getDelete() && !processor.hasRulesyncSourceLoadFailure()) sweepPlan.defer({
|
|
94192
95704
|
sweep: async () => {
|
|
94193
95705
|
const existingToolFiles = await processor.loadToolFiles({ forDeletion: true });
|
|
94194
|
-
const filesToDelete = sweepPlan.rejectClaimed({
|
|
95706
|
+
const filesToDelete = (await sweepPlan.rejectClaimed({
|
|
94195
95707
|
items: existingToolFiles,
|
|
94196
95708
|
getPath: (f) => f.getFilePath()
|
|
94197
|
-
}).filter((f) => !skipFilePaths?.has(f.getRelativePathFromCwd()));
|
|
95709
|
+
})).filter((f) => !skipFilePaths?.has(f.getRelativePathFromCwd()));
|
|
94198
95710
|
return await processor.removeOrphanAiFiles(filesToDelete, []) > 0;
|
|
94199
95711
|
},
|
|
94200
95712
|
reportDeleted: () => processor.getRemovedPaths()
|
|
@@ -94520,7 +96032,7 @@ async function generate(params) {
|
|
|
94520
96032
|
logger
|
|
94521
96033
|
});
|
|
94522
96034
|
let skillsResult;
|
|
94523
|
-
const sweepPlan = createOrphanSweepPlan();
|
|
96035
|
+
const sweepPlan = createOrphanSweepPlan({ logger });
|
|
94524
96036
|
const runners = {
|
|
94525
96037
|
ignore: () => generateIgnoreCore({
|
|
94526
96038
|
config,
|
|
@@ -95695,6 +97207,12 @@ Object.defineProperty(exports, "AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME", {
|
|
|
95695
97207
|
return AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME;
|
|
95696
97208
|
}
|
|
95697
97209
|
});
|
|
97210
|
+
Object.defineProperty(exports, "CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME", {
|
|
97211
|
+
enumerable: true,
|
|
97212
|
+
get: function() {
|
|
97213
|
+
return CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME;
|
|
97214
|
+
}
|
|
97215
|
+
});
|
|
95698
97216
|
Object.defineProperty(exports, "CLAUDECODE_DIR", {
|
|
95699
97217
|
enumerable: true,
|
|
95700
97218
|
get: function() {
|