rulesync 21.0.0 → 22.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CGT8v9bO.cjs → import-1vyJw3q0.cjs} +417 -187
- package/dist/{import-CSKYRGa5.js → import-DNDrEvS2.js} +401 -171
- package/dist/import-DNDrEvS2.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-CSKYRGa5.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$351 } = 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$351(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$351(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
460
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
461
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
462
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
463
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
464
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
465
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
466
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
467
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
468
|
+
join$351(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
469
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
470
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$351(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$351(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$351(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$351(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";
|
|
@@ -12295,7 +12295,7 @@ const CODEXCLI_AGENTS_DIR_PATH = (0, node_path.join)(CODEXCLI_DIR, "agents");
|
|
|
12295
12295
|
const CODEXCLI_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
12296
12296
|
const CODEXCLI_HOOKS_FILE_NAME = "hooks.json";
|
|
12297
12297
|
const CODEXCLI_MCP_FILE_NAME = "config.toml";
|
|
12298
|
-
const
|
|
12298
|
+
const CODEXCLI_PROJECT_DOC_MAX_BYTES = 32768;
|
|
12299
12299
|
const CODEXCLI_BASH_RULES_FILE_NAME = "rulesync.rules";
|
|
12300
12300
|
const CODEXCLI_OPENAI_YAML_RELATIVE_PATH = (0, node_path.join)("agents", "openai.yaml");
|
|
12301
12301
|
const CODEXCLI_OVERRIDE_KEYS = [
|
|
@@ -15480,12 +15480,50 @@ var BobCommand = class BobCommand extends ToolCommand {
|
|
|
15480
15480
|
}
|
|
15481
15481
|
};
|
|
15482
15482
|
//#endregion
|
|
15483
|
+
//#region src/features/shared/claudecode-argument-hint.ts
|
|
15484
|
+
/**
|
|
15485
|
+
* Turns a YAML-list `argument-hint` back into the bracketed string form Claude
|
|
15486
|
+
* Code documents (`[issue-number]`, `[filename] [format]`), one bracketed
|
|
15487
|
+
* placeholder per list entry. The per-entry form is chosen over re-joining with
|
|
15488
|
+
* commas because it is the shape every Claude Code example uses. An empty list
|
|
15489
|
+
* carries no hint, so it yields `undefined` and the key is dropped.
|
|
15490
|
+
*/
|
|
15491
|
+
function joinArgumentHint(value) {
|
|
15492
|
+
if (typeof value === "string") return value;
|
|
15493
|
+
if (value.length === 0) return;
|
|
15494
|
+
return value.map((entry) => `[${String(entry)}]`).join(" ");
|
|
15495
|
+
}
|
|
15496
|
+
/**
|
|
15497
|
+
* `argument-hint` as Claude Code reads it from skill and command frontmatter.
|
|
15498
|
+
*
|
|
15499
|
+
* The documented examples (`[issue-number]`) are written unquoted, and YAML
|
|
15500
|
+
* parses an unquoted `[issue-number]` as a one-element list. Claude Code
|
|
15501
|
+
* accepts that form, so it is accepted here too and normalized to the
|
|
15502
|
+
* bracketed string, keeping the parsed value (and everything downstream of it)
|
|
15503
|
+
* a plain string. A scalar entry YAML did not read as a string (`[1]`,
|
|
15504
|
+
* `[true]`, `[~]`) is stringified rather than rejected.
|
|
15505
|
+
* https://code.claude.com/docs/en/skills
|
|
15506
|
+
*
|
|
15507
|
+
* A list entry is stringified from the value YAML produced, so its original
|
|
15508
|
+
* spelling is not kept (`[1.0]` becomes `[1]`), and an entry YAML reads as a
|
|
15509
|
+
* date (`[2026-01-01]`) is rejected.
|
|
15510
|
+
*
|
|
15511
|
+
* The unquoted multi-placeholder form (`argument-hint: [filename] [format]`)
|
|
15512
|
+
* is not valid YAML at all, so it fails before this schema is reached.
|
|
15513
|
+
*/
|
|
15514
|
+
const ClaudecodeArgumentHintSchema = zod_mini.z.pipe(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.union([
|
|
15515
|
+
zod_mini.z.string(),
|
|
15516
|
+
zod_mini.z.number(),
|
|
15517
|
+
zod_mini.z.boolean(),
|
|
15518
|
+
zod_mini.z.null()
|
|
15519
|
+
]))]), zod_mini.z.transform(joinArgumentHint));
|
|
15520
|
+
//#endregion
|
|
15483
15521
|
//#region src/features/commands/claudecode-command.ts
|
|
15484
15522
|
const ClaudecodeCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
15485
15523
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
15486
15524
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
15487
15525
|
"disallowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
15488
|
-
"argument-hint": zod_mini.z.optional(
|
|
15526
|
+
"argument-hint": zod_mini.z.optional(ClaudecodeArgumentHintSchema),
|
|
15489
15527
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
15490
15528
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
15491
15529
|
});
|
|
@@ -15514,7 +15552,8 @@ var ClaudecodeCommand = class ClaudecodeCommand extends ToolCommand {
|
|
|
15514
15552
|
return this.frontmatter;
|
|
15515
15553
|
}
|
|
15516
15554
|
toRulesyncCommand() {
|
|
15517
|
-
const { description, ...
|
|
15555
|
+
const { description, ...extraFields } = this.frontmatter;
|
|
15556
|
+
const restFields = Object.fromEntries(Object.entries(extraFields).filter(([, value]) => value !== void 0));
|
|
15518
15557
|
const rulesyncFrontmatter = {
|
|
15519
15558
|
targets: ["*"],
|
|
15520
15559
|
description,
|
|
@@ -33147,6 +33186,74 @@ function translateCanonicalKeyToCodex({ key, value, isStdio, serverName }) {
|
|
|
33147
33186
|
}
|
|
33148
33187
|
}
|
|
33149
33188
|
/**
|
|
33189
|
+
* A header value that is exactly one canonical `${VAR}` reference. Anchored and
|
|
33190
|
+
* without the `g` flag, so `.exec()` carries no `lastIndex` state between
|
|
33191
|
+
* calls. `${VAR:-default}` does not match (the name excludes `:`), because
|
|
33192
|
+
* Codex has no way to express the fallback.
|
|
33193
|
+
*/
|
|
33194
|
+
const WHOLE_ENV_VAR_REF_PATTERN = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
33195
|
+
/**
|
|
33196
|
+
* An `Authorization` value that is exactly `Bearer ${VAR}`. The scheme is
|
|
33197
|
+
* matched case-insensitively, as HTTP authentication schemes are.
|
|
33198
|
+
*/
|
|
33199
|
+
const BEARER_ENV_VAR_REF_PATTERN = /^Bearer \$\{([A-Za-z_][A-Za-z0-9_]*)\}$/i;
|
|
33200
|
+
const AUTHORIZATION_HEADER_NAME = "authorization";
|
|
33201
|
+
/**
|
|
33202
|
+
* Whether a string still contains an unexpanded `${…}` reference. Codex writes
|
|
33203
|
+
* `url` and `http_headers` values as-is and never expands them, so such a value
|
|
33204
|
+
* reaches the server literally.
|
|
33205
|
+
*/
|
|
33206
|
+
function hasUnexpandedEnvVarRef(value) {
|
|
33207
|
+
return value.includes("${");
|
|
33208
|
+
}
|
|
33209
|
+
/**
|
|
33210
|
+
* Rewrite canonical `${VAR}` header references into the keys Codex reads them
|
|
33211
|
+
* from, since Codex sends `http_headers` values verbatim:
|
|
33212
|
+
* - `Authorization: Bearer ${VAR}` → `bearer_token_env_var = "VAR"`
|
|
33213
|
+
* - a header whose whole value is `${VAR}` → `env_http_headers.<name> = "VAR"`
|
|
33214
|
+
*
|
|
33215
|
+
* Runs after every key is translated, so a `bearer_token_env_var` or
|
|
33216
|
+
* `env_http_headers` the source already carries (they pass through under their
|
|
33217
|
+
* own names) is seen regardless of key order. Those explicit values win: a
|
|
33218
|
+
* derived bearer is not written over an existing one, and an existing
|
|
33219
|
+
* `env_http_headers` entry for the same header is kept. Any value the rewrite
|
|
33220
|
+
* cannot express — a partial reference, a `${VAR:-default}` fallback — stays in
|
|
33221
|
+
* `http_headers` verbatim with a warning, as does a `${…}` in `url`, for which
|
|
33222
|
+
* Codex has no environment-sourced counterpart at all.
|
|
33223
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
33224
|
+
*/
|
|
33225
|
+
function convertEnvVarRefsForCodex({ converted, serverName }) {
|
|
33226
|
+
const url = converted["url"];
|
|
33227
|
+
if (typeof url === "string" && hasUnexpandedEnvVarRef(url)) warnWithFallback(void 0, `[CodexCliMcp] MCP server "${serverName}": 'url' contains an environment variable reference that Codex does not expand, so it is written verbatim and the server will likely fail to start.`);
|
|
33228
|
+
const httpHeaders = converted["http_headers"];
|
|
33229
|
+
if (!isHeadersRecord(httpHeaders)) return;
|
|
33230
|
+
const existingEnvHeaders = converted["env_http_headers"];
|
|
33231
|
+
const canWriteEnvHeaders = existingEnvHeaders === void 0 || isHeadersRecord(existingEnvHeaders);
|
|
33232
|
+
const envHeaders = {};
|
|
33233
|
+
const staticHeaders = {};
|
|
33234
|
+
let bearerTokenEnvVar;
|
|
33235
|
+
for (const [name, value] of Object.entries(httpHeaders)) {
|
|
33236
|
+
const bearerMatch = BEARER_ENV_VAR_REF_PATTERN.exec(value);
|
|
33237
|
+
if (bearerMatch?.[1] && name.toLowerCase() === AUTHORIZATION_HEADER_NAME) {
|
|
33238
|
+
if (converted["bearer_token_env_var"] === void 0 && bearerTokenEnvVar === void 0) bearerTokenEnvVar = bearerMatch[1];
|
|
33239
|
+
continue;
|
|
33240
|
+
}
|
|
33241
|
+
const wholeMatch = WHOLE_ENV_VAR_REF_PATTERN.exec(value);
|
|
33242
|
+
if (wholeMatch?.[1] && canWriteEnvHeaders) {
|
|
33243
|
+
envHeaders[name] = wholeMatch[1];
|
|
33244
|
+
continue;
|
|
33245
|
+
}
|
|
33246
|
+
if (hasUnexpandedEnvVarRef(value)) warnWithFallback(void 0, `[CodexCliMcp] MCP server "${serverName}": header '${name}' contains an environment variable reference that Codex cannot source from the environment, so it is written verbatim to 'http_headers' and sent literally. Only 'Authorization: Bearer \${VAR}' and a whole-value '\${VAR}' are converted.`);
|
|
33247
|
+
staticHeaders[name] = value;
|
|
33248
|
+
}
|
|
33249
|
+
converted["http_headers"] = staticHeaders;
|
|
33250
|
+
if (bearerTokenEnvVar !== void 0) converted["bearer_token_env_var"] = bearerTokenEnvVar;
|
|
33251
|
+
if (Object.keys(envHeaders).length > 0) converted["env_http_headers"] = {
|
|
33252
|
+
...envHeaders,
|
|
33253
|
+
...isHeadersRecord(existingEnvHeaders) ? existingEnvHeaders : {}
|
|
33254
|
+
};
|
|
33255
|
+
}
|
|
33256
|
+
/**
|
|
33150
33257
|
* Codex states no transport of its own — it infers one from `command` versus
|
|
33151
33258
|
* `url`, which is why generate drops the canonical `type`. Restate it for a url
|
|
33152
33259
|
* server on the way back, so a config imported from Codex reaches the adapters
|
|
@@ -33233,6 +33340,10 @@ function convertToCodexFormat(mcpServers) {
|
|
|
33233
33340
|
}
|
|
33234
33341
|
} else converted[key] = value;
|
|
33235
33342
|
}
|
|
33343
|
+
convertEnvVarRefsForCodex({
|
|
33344
|
+
converted,
|
|
33345
|
+
serverName: name
|
|
33346
|
+
});
|
|
33236
33347
|
const previousName = originalNames.get(codexName);
|
|
33237
33348
|
if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
|
|
33238
33349
|
originalNames.set(codexName, name);
|
|
@@ -59232,7 +59343,7 @@ const ClaudecodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
59232
59343
|
"disallowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
59233
59344
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
59234
59345
|
effort: zod_mini.z.optional(zod_mini.z.string()),
|
|
59235
|
-
"argument-hint": zod_mini.z.optional(
|
|
59346
|
+
"argument-hint": zod_mini.z.optional(ClaudecodeArgumentHintSchema),
|
|
59236
59347
|
arguments: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
59237
59348
|
context: zod_mini.z.optional(zod_mini.z.string()),
|
|
59238
59349
|
agent: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -65388,7 +65499,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
65388
65499
|
meta: {
|
|
65389
65500
|
supportsProject: true,
|
|
65390
65501
|
supportsSimulated: false,
|
|
65391
|
-
supportsGlobal: true
|
|
65502
|
+
supportsGlobal: true,
|
|
65503
|
+
lenientImport: true
|
|
65392
65504
|
}
|
|
65393
65505
|
}],
|
|
65394
65506
|
["claudecode-plugin", {
|
|
@@ -72337,7 +72449,7 @@ var ToolRule = class extends ToolFile {
|
|
|
72337
72449
|
static buildToolRuleParamsAgentsmd({ outputRoot = process.cwd(), rulesyncRule, validate = true, rootPath = {
|
|
72338
72450
|
relativeDirPath: ".",
|
|
72339
72451
|
relativeFilePath: "AGENTS.md"
|
|
72340
|
-
}, nonRootPath = { relativeDirPath: AGENTSMD_MEMORIES_DIR_PATH } }) {
|
|
72452
|
+
}, nonRootPath = { relativeDirPath: AGENTSMD_MEMORIES_DIR_PATH }, global = false }) {
|
|
72341
72453
|
const params = this.buildToolRuleParamsDefault({
|
|
72342
72454
|
outputRoot,
|
|
72343
72455
|
rulesyncRule,
|
|
@@ -72346,7 +72458,7 @@ var ToolRule = class extends ToolFile {
|
|
|
72346
72458
|
nonRootPath
|
|
72347
72459
|
});
|
|
72348
72460
|
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
72349
|
-
if (!rulesyncFrontmatter.root && rulesyncFrontmatter.agentsmd?.subprojectPath) {
|
|
72461
|
+
if (!global && !rulesyncFrontmatter.root && rulesyncFrontmatter.agentsmd?.subprojectPath) {
|
|
72350
72462
|
params.relativeDirPath = (0, node_path.join)(rulesyncFrontmatter.agentsmd.subprojectPath);
|
|
72351
72463
|
params.relativeFilePath = "AGENTS.md";
|
|
72352
72464
|
}
|
|
@@ -73003,7 +73115,8 @@ var AmpRule = class AmpRule extends ToolRule {
|
|
|
73003
73115
|
rulesyncRule,
|
|
73004
73116
|
validate,
|
|
73005
73117
|
rootPath: paths.root,
|
|
73006
|
-
nonRootPath: paths.nonRoot
|
|
73118
|
+
nonRootPath: paths.nonRoot,
|
|
73119
|
+
global
|
|
73007
73120
|
});
|
|
73008
73121
|
if (!params.root && params.globs !== void 0 && params.globs.length > 0) params.fileContent = stringifyFrontmatter(params.fileContent, { globs: params.globs });
|
|
73009
73122
|
return new AmpRule(params);
|
|
@@ -74298,7 +74411,8 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
74298
74411
|
outputRoot,
|
|
74299
74412
|
rulesyncRule,
|
|
74300
74413
|
validate,
|
|
74301
|
-
rootPath: paths.root
|
|
74414
|
+
rootPath: paths.root,
|
|
74415
|
+
global
|
|
74302
74416
|
}));
|
|
74303
74417
|
}
|
|
74304
74418
|
if (isRoot) return new ClineRule({
|
|
@@ -74718,26 +74832,62 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
74718
74832
|
}
|
|
74719
74833
|
};
|
|
74720
74834
|
//#endregion
|
|
74721
|
-
//#region src/features/rules/
|
|
74722
|
-
|
|
74723
|
-
|
|
74724
|
-
|
|
74725
|
-
|
|
74726
|
-
|
|
74727
|
-
|
|
74728
|
-
|
|
74835
|
+
//#region src/features/rules/nested-agentsmd-rule.ts
|
|
74836
|
+
/**
|
|
74837
|
+
* Shared adapter for the targets that read instructions exactly the way the
|
|
74838
|
+
* AGENTS.md standard describes them and nothing more: a root `AGENTS.md` at the
|
|
74839
|
+
* project root, nested per-directory `AGENTS.md` files loaded only while working
|
|
74840
|
+
* under that directory, and a single personal root file under a tool-specific
|
|
74841
|
+
* home directory. Such a target has no modular non-root instruction directory,
|
|
74842
|
+
* so every other non-root rule folds into the root file.
|
|
74843
|
+
*
|
|
74844
|
+
* Concrete targets (Codex CLI, Pool, Vibe, DeepSeek Harness) only supply the global
|
|
74845
|
+
* directory and their tool target through {@link getFamily}; the vendor docs
|
|
74846
|
+
* describing each one's discovery walk live on the subclass.
|
|
74847
|
+
*
|
|
74848
|
+
* The nested-file scan mirrors the AGENTS.md standard's nested discovery —
|
|
74849
|
+
* same file name, same exclusions, import-only, project scope — because it
|
|
74850
|
+
* discovers literally the same files.
|
|
74851
|
+
*/
|
|
74852
|
+
var NestedAgentsmdRule = class extends ToolRule {
|
|
74853
|
+
static getFamily() {
|
|
74854
|
+
throw new Error("Please implement this method in the subclass.");
|
|
74729
74855
|
}
|
|
74730
74856
|
static getSettablePaths({ global = false } = {}) {
|
|
74857
|
+
if (global) return { root: {
|
|
74858
|
+
relativeDirPath: this.getFamily().globalDir,
|
|
74859
|
+
relativeFilePath: AGENTSMD_RULE_FILE_NAME
|
|
74860
|
+
} };
|
|
74731
74861
|
return { root: {
|
|
74732
|
-
relativeDirPath:
|
|
74733
|
-
relativeFilePath:
|
|
74862
|
+
relativeDirPath: ".",
|
|
74863
|
+
relativeFilePath: AGENTSMD_RULE_FILE_NAME
|
|
74734
74864
|
} };
|
|
74735
74865
|
}
|
|
74736
|
-
static
|
|
74866
|
+
static getNestedFilePatterns() {
|
|
74867
|
+
return this.buildNestedFilePatterns({ fileName: AGENTSMD_RULE_FILE_NAME });
|
|
74868
|
+
}
|
|
74869
|
+
/**
|
|
74870
|
+
* The subproject directory this rule scopes, or `undefined` for the root file
|
|
74871
|
+
* (project or global).
|
|
74872
|
+
*/
|
|
74873
|
+
getSubprojectPath() {
|
|
74874
|
+
return this.getNestedSubprojectPath({ fileName: AGENTSMD_RULE_FILE_NAME });
|
|
74875
|
+
}
|
|
74876
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
74737
74877
|
const { root } = this.getSettablePaths({ global });
|
|
74738
|
-
|
|
74739
|
-
|
|
74740
|
-
|
|
74878
|
+
if (overrideDirPath !== void 0 && overrideDirPath !== root.relativeDirPath && overrideDirPath !== ".") {
|
|
74879
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, overrideDirPath, AGENTSMD_RULE_FILE_NAME));
|
|
74880
|
+
return new this({
|
|
74881
|
+
outputRoot,
|
|
74882
|
+
relativeDirPath: overrideDirPath,
|
|
74883
|
+
relativeFilePath: AGENTSMD_RULE_FILE_NAME,
|
|
74884
|
+
fileContent,
|
|
74885
|
+
validate,
|
|
74886
|
+
root: false
|
|
74887
|
+
});
|
|
74888
|
+
}
|
|
74889
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, root.relativeDirPath, root.relativeFilePath));
|
|
74890
|
+
return new this({
|
|
74741
74891
|
outputRoot,
|
|
74742
74892
|
relativeDirPath: root.relativeDirPath,
|
|
74743
74893
|
relativeFilePath: root.relativeFilePath,
|
|
@@ -74748,8 +74898,18 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
74748
74898
|
}
|
|
74749
74899
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
74750
74900
|
const { root } = this.getSettablePaths({ global });
|
|
74751
|
-
const
|
|
74752
|
-
|
|
74901
|
+
const frontmatter = rulesyncRule.getFrontmatter();
|
|
74902
|
+
const isRoot = frontmatter.root ?? false;
|
|
74903
|
+
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
74904
|
+
if (!global && !isRoot && subprojectPath) return new this({
|
|
74905
|
+
outputRoot,
|
|
74906
|
+
relativeDirPath: (0, node_path.join)(subprojectPath),
|
|
74907
|
+
relativeFilePath: AGENTSMD_RULE_FILE_NAME,
|
|
74908
|
+
fileContent: rulesyncRule.getBody(),
|
|
74909
|
+
validate,
|
|
74910
|
+
root: false
|
|
74911
|
+
});
|
|
74912
|
+
return new this({
|
|
74753
74913
|
outputRoot,
|
|
74754
74914
|
relativeDirPath: root.relativeDirPath,
|
|
74755
74915
|
relativeFilePath: root.relativeFilePath,
|
|
@@ -74759,7 +74919,9 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
74759
74919
|
});
|
|
74760
74920
|
}
|
|
74761
74921
|
toRulesyncRule() {
|
|
74762
|
-
|
|
74922
|
+
const subprojectPath = this.getSubprojectPath();
|
|
74923
|
+
if (subprojectPath === void 0) return this.toRulesyncRuleDefault();
|
|
74924
|
+
return this.toRulesyncRuleNestedAgentsmd({ subprojectPath });
|
|
74763
74925
|
}
|
|
74764
74926
|
validate() {
|
|
74765
74927
|
return {
|
|
@@ -74768,23 +74930,54 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
74768
74930
|
};
|
|
74769
74931
|
}
|
|
74770
74932
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
74771
|
-
|
|
74933
|
+
const isRoot = relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === this.getFamily().globalDir);
|
|
74934
|
+
return new this({
|
|
74772
74935
|
outputRoot,
|
|
74773
74936
|
relativeDirPath,
|
|
74774
74937
|
relativeFilePath,
|
|
74775
74938
|
fileContent: "",
|
|
74776
74939
|
validate: false,
|
|
74777
|
-
root:
|
|
74940
|
+
root: isRoot
|
|
74778
74941
|
});
|
|
74779
74942
|
}
|
|
74780
74943
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
74781
74944
|
return this.isTargetedByRulesyncRuleDefault({
|
|
74782
74945
|
rulesyncRule,
|
|
74783
|
-
toolTarget:
|
|
74946
|
+
toolTarget: this.getFamily().toolTarget
|
|
74784
74947
|
});
|
|
74785
74948
|
}
|
|
74786
74949
|
};
|
|
74787
74950
|
//#endregion
|
|
74951
|
+
//#region src/features/rules/codexcli-rule.ts
|
|
74952
|
+
/**
|
|
74953
|
+
* Rule generator for OpenAI Codex CLI.
|
|
74954
|
+
*
|
|
74955
|
+
* Codex CLI loads instructions only from the `AGENTS.md` family: the global
|
|
74956
|
+
* `~/.codex/AGENTS.md` (or `AGENTS.override.md`), then one file per directory
|
|
74957
|
+
* level from the project root down to the current working directory, joined
|
|
74958
|
+
* root-first so deeper files take precedence. Nested per-directory files are
|
|
74959
|
+
* therefore a real scoping surface, and Codex's docs recommend splitting large
|
|
74960
|
+
* instruction sets across nested directories. It does NOT scan a
|
|
74961
|
+
* `.codex/memories/` directory for instruction files — that directory belongs
|
|
74962
|
+
* to Codex's separate SQLite-backed auto-memory system.
|
|
74963
|
+
*
|
|
74964
|
+
* A non-root rule carrying `agentsmd.subprojectPath` is emitted as a nested
|
|
74965
|
+
* `<subprojectPath>/AGENTS.md` (project scope only); every other non-root rule
|
|
74966
|
+
* folds into the root `AGENTS.md`, since Codex has no modular rules directory.
|
|
74967
|
+
* The project files on the root-to-cwd chain share one `project_doc_max_bytes`
|
|
74968
|
+
* budget (32 KiB by default), which the RulesProcessor warns about.
|
|
74969
|
+
*
|
|
74970
|
+
* @see https://learn.chatgpt.com/docs/agent-configuration/agents-md
|
|
74971
|
+
*/
|
|
74972
|
+
var CodexcliRule = class extends NestedAgentsmdRule {
|
|
74973
|
+
static getFamily() {
|
|
74974
|
+
return {
|
|
74975
|
+
globalDir: CODEXCLI_DIR,
|
|
74976
|
+
toolTarget: "codexcli"
|
|
74977
|
+
};
|
|
74978
|
+
}
|
|
74979
|
+
};
|
|
74980
|
+
//#endregion
|
|
74788
74981
|
//#region src/features/rules/commandcode-rule.ts
|
|
74789
74982
|
var CommandcodeRule = class CommandcodeRule extends ToolRule {
|
|
74790
74983
|
constructor({ fileContent, root, ...rest }) {
|
|
@@ -76089,122 +76282,6 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
76089
76282
|
}
|
|
76090
76283
|
};
|
|
76091
76284
|
//#endregion
|
|
76092
|
-
//#region src/features/rules/nested-agentsmd-rule.ts
|
|
76093
|
-
/**
|
|
76094
|
-
* Shared adapter for the targets that read instructions exactly the way the
|
|
76095
|
-
* AGENTS.md standard describes them and nothing more: a root `AGENTS.md` at the
|
|
76096
|
-
* project root, nested per-directory `AGENTS.md` files loaded only while working
|
|
76097
|
-
* under that directory, and a single personal root file under a tool-specific
|
|
76098
|
-
* home directory. Such a target has no modular non-root instruction directory,
|
|
76099
|
-
* so every other non-root rule folds into the root file.
|
|
76100
|
-
*
|
|
76101
|
-
* Concrete targets (Pool, Vibe, DeepSeek Harness) only supply the global
|
|
76102
|
-
* directory and their tool target through {@link getFamily}; the vendor docs
|
|
76103
|
-
* describing each one's discovery walk live on the subclass.
|
|
76104
|
-
*
|
|
76105
|
-
* The nested-file scan mirrors the AGENTS.md standard's nested discovery —
|
|
76106
|
-
* same file name, same exclusions, import-only, project scope — because it
|
|
76107
|
-
* discovers literally the same files.
|
|
76108
|
-
*/
|
|
76109
|
-
var NestedAgentsmdRule = class extends ToolRule {
|
|
76110
|
-
static getFamily() {
|
|
76111
|
-
throw new Error("Please implement this method in the subclass.");
|
|
76112
|
-
}
|
|
76113
|
-
static getSettablePaths({ global = false } = {}) {
|
|
76114
|
-
if (global) return { root: {
|
|
76115
|
-
relativeDirPath: this.getFamily().globalDir,
|
|
76116
|
-
relativeFilePath: AGENTSMD_RULE_FILE_NAME
|
|
76117
|
-
} };
|
|
76118
|
-
return { root: {
|
|
76119
|
-
relativeDirPath: ".",
|
|
76120
|
-
relativeFilePath: AGENTSMD_RULE_FILE_NAME
|
|
76121
|
-
} };
|
|
76122
|
-
}
|
|
76123
|
-
static getNestedFilePatterns() {
|
|
76124
|
-
return this.buildNestedFilePatterns({ fileName: AGENTSMD_RULE_FILE_NAME });
|
|
76125
|
-
}
|
|
76126
|
-
/**
|
|
76127
|
-
* The subproject directory this rule scopes, or `undefined` for the root file
|
|
76128
|
-
* (project or global).
|
|
76129
|
-
*/
|
|
76130
|
-
getSubprojectPath() {
|
|
76131
|
-
return this.getNestedSubprojectPath({ fileName: AGENTSMD_RULE_FILE_NAME });
|
|
76132
|
-
}
|
|
76133
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
76134
|
-
const { root } = this.getSettablePaths({ global });
|
|
76135
|
-
if (overrideDirPath !== void 0 && overrideDirPath !== root.relativeDirPath && overrideDirPath !== ".") {
|
|
76136
|
-
const fileContent = await readFileContent((0, node_path.join)(outputRoot, overrideDirPath, AGENTSMD_RULE_FILE_NAME));
|
|
76137
|
-
return new this({
|
|
76138
|
-
outputRoot,
|
|
76139
|
-
relativeDirPath: overrideDirPath,
|
|
76140
|
-
relativeFilePath: AGENTSMD_RULE_FILE_NAME,
|
|
76141
|
-
fileContent,
|
|
76142
|
-
validate,
|
|
76143
|
-
root: false
|
|
76144
|
-
});
|
|
76145
|
-
}
|
|
76146
|
-
const fileContent = await readFileContent((0, node_path.join)(outputRoot, root.relativeDirPath, root.relativeFilePath));
|
|
76147
|
-
return new this({
|
|
76148
|
-
outputRoot,
|
|
76149
|
-
relativeDirPath: root.relativeDirPath,
|
|
76150
|
-
relativeFilePath: root.relativeFilePath,
|
|
76151
|
-
fileContent,
|
|
76152
|
-
validate,
|
|
76153
|
-
root: true
|
|
76154
|
-
});
|
|
76155
|
-
}
|
|
76156
|
-
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
76157
|
-
const { root } = this.getSettablePaths({ global });
|
|
76158
|
-
const frontmatter = rulesyncRule.getFrontmatter();
|
|
76159
|
-
const isRoot = frontmatter.root ?? false;
|
|
76160
|
-
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
76161
|
-
if (!global && !isRoot && subprojectPath) return new this({
|
|
76162
|
-
outputRoot,
|
|
76163
|
-
relativeDirPath: (0, node_path.join)(subprojectPath),
|
|
76164
|
-
relativeFilePath: AGENTSMD_RULE_FILE_NAME,
|
|
76165
|
-
fileContent: rulesyncRule.getBody(),
|
|
76166
|
-
validate,
|
|
76167
|
-
root: false
|
|
76168
|
-
});
|
|
76169
|
-
return new this({
|
|
76170
|
-
outputRoot,
|
|
76171
|
-
relativeDirPath: root.relativeDirPath,
|
|
76172
|
-
relativeFilePath: root.relativeFilePath,
|
|
76173
|
-
fileContent: rulesyncRule.getBody(),
|
|
76174
|
-
validate,
|
|
76175
|
-
root: isRoot
|
|
76176
|
-
});
|
|
76177
|
-
}
|
|
76178
|
-
toRulesyncRule() {
|
|
76179
|
-
const subprojectPath = this.getSubprojectPath();
|
|
76180
|
-
if (subprojectPath === void 0) return this.toRulesyncRuleDefault();
|
|
76181
|
-
return this.toRulesyncRuleNestedAgentsmd({ subprojectPath });
|
|
76182
|
-
}
|
|
76183
|
-
validate() {
|
|
76184
|
-
return {
|
|
76185
|
-
success: true,
|
|
76186
|
-
error: null
|
|
76187
|
-
};
|
|
76188
|
-
}
|
|
76189
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
76190
|
-
const isRoot = relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === this.getFamily().globalDir);
|
|
76191
|
-
return new this({
|
|
76192
|
-
outputRoot,
|
|
76193
|
-
relativeDirPath,
|
|
76194
|
-
relativeFilePath,
|
|
76195
|
-
fileContent: "",
|
|
76196
|
-
validate: false,
|
|
76197
|
-
root: isRoot
|
|
76198
|
-
});
|
|
76199
|
-
}
|
|
76200
|
-
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
76201
|
-
return this.isTargetedByRulesyncRuleDefault({
|
|
76202
|
-
rulesyncRule,
|
|
76203
|
-
toolTarget: this.getFamily().toolTarget
|
|
76204
|
-
});
|
|
76205
|
-
}
|
|
76206
|
-
};
|
|
76207
|
-
//#endregion
|
|
76208
76285
|
//#region src/features/rules/dsh-rule.ts
|
|
76209
76286
|
/**
|
|
76210
76287
|
* DeepSeek Harness (`dsh`) rules.
|
|
@@ -76416,7 +76493,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
76416
76493
|
rulesyncRule,
|
|
76417
76494
|
validate,
|
|
76418
76495
|
rootPath: paths.root,
|
|
76419
|
-
nonRootPath: paths.nonRoot
|
|
76496
|
+
nonRootPath: paths.nonRoot,
|
|
76497
|
+
global
|
|
76420
76498
|
}));
|
|
76421
76499
|
}
|
|
76422
76500
|
toRulesyncRule() {
|
|
@@ -76883,7 +76961,8 @@ var KiloRule = class KiloRule extends ToolRule {
|
|
|
76883
76961
|
rulesyncRule,
|
|
76884
76962
|
validate,
|
|
76885
76963
|
rootPath: paths.root,
|
|
76886
|
-
nonRootPath: paths.nonRoot
|
|
76964
|
+
nonRootPath: paths.nonRoot,
|
|
76965
|
+
global
|
|
76887
76966
|
}));
|
|
76888
76967
|
}
|
|
76889
76968
|
toRulesyncRule() {
|
|
@@ -77387,7 +77466,8 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
77387
77466
|
rulesyncRule,
|
|
77388
77467
|
validate,
|
|
77389
77468
|
rootPath: paths.root,
|
|
77390
|
-
nonRootPath: paths.nonRoot
|
|
77469
|
+
nonRootPath: paths.nonRoot,
|
|
77470
|
+
global
|
|
77391
77471
|
}));
|
|
77392
77472
|
}
|
|
77393
77473
|
toRulesyncRule() {
|
|
@@ -78856,6 +78936,14 @@ var ZoocodeRule = class extends RooRule {
|
|
|
78856
78936
|
//#endregion
|
|
78857
78937
|
//#region src/features/rules/rules-processor.ts
|
|
78858
78938
|
const RulesProcessorToolTargetSchema = zod_mini.z.enum(rulesProcessorToolTargetTuple);
|
|
78939
|
+
/**
|
|
78940
|
+
* Normalizes a relative directory path for comparison: POSIX separators, no
|
|
78941
|
+
* `./` segments and no trailing slash, with the current directory as `"."`
|
|
78942
|
+
* (so `"./"`, `"a/"` and `"a"` compare the way `join()` groups them).
|
|
78943
|
+
*/
|
|
78944
|
+
const normalizeRelativeDir = (dir) => node_path.posix.normalize(toPosixPath(dir)).replace(/\/+$/, "") || ".";
|
|
78945
|
+
/** A tool rule's output path relative to its output root, for comparison. */
|
|
78946
|
+
const toolOutputPath = (toolRule) => node_path.posix.join(normalizeRelativeDir(toolRule.getRelativeDirPath()), toolRule.getRelativeFilePath());
|
|
78859
78947
|
const formatRulePaths = (rules) => rules.map((r) => (0, node_path.join)(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
|
|
78860
78948
|
const RulesFeatureOptionsSchema = zod_mini.z.looseObject({
|
|
78861
78949
|
ruleDiscoveryMode: zod_mini.z.optional(zod_mini.z.enum(["none", "explicit"])),
|
|
@@ -79024,7 +79112,11 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
79024
79112
|
extension: "md",
|
|
79025
79113
|
supportsGlobal: true,
|
|
79026
79114
|
ruleDiscoveryMode: "auto",
|
|
79027
|
-
collisionPolicy: "fold"
|
|
79115
|
+
collisionPolicy: "fold",
|
|
79116
|
+
projectInstructionBudget: {
|
|
79117
|
+
bytes: CODEXCLI_PROJECT_DOC_MAX_BYTES,
|
|
79118
|
+
remedy: "Codex CLI stops reading project `AGENTS.md` files once their combined size on the root-to-working-directory chain reaches `project_doc_max_bytes` (32 KiB by default) and silently drops the rest. Move directory-scoped rules into nested `AGENTS.md` files with `agentsmd.subprojectPath` (or `deriveSubprojectPathFromGlobs: true` in rulesync.jsonc), trim the rules, or raise `project_doc_max_bytes` in `.codex/config.toml` (ignore this warning if you already have)."
|
|
79119
|
+
}
|
|
79028
79120
|
}
|
|
79029
79121
|
}],
|
|
79030
79122
|
["commandcode", {
|
|
@@ -79482,9 +79574,61 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
79482
79574
|
toolRules,
|
|
79483
79575
|
factory
|
|
79484
79576
|
});
|
|
79577
|
+
this.warnForInstructionBudget({
|
|
79578
|
+
toolRules,
|
|
79579
|
+
meta
|
|
79580
|
+
});
|
|
79485
79581
|
return outputFiles;
|
|
79486
79582
|
}
|
|
79487
79583
|
/**
|
|
79584
|
+
* Warn when a generated project instruction chain exceeds the tool's byte
|
|
79585
|
+
* budget (`meta.projectInstructionBudget`).
|
|
79586
|
+
*
|
|
79587
|
+
* Codex CLI concatenates the project `AGENTS.md` files from the project root
|
|
79588
|
+
* down to the cwd and stops once their combined size reaches
|
|
79589
|
+
* `project_doc_max_bytes`, truncating the file that crosses it — silently, so
|
|
79590
|
+
* without this warning the rules past the limit simply never reach the model.
|
|
79591
|
+
* Each generated file is measured together with the generated files in its
|
|
79592
|
+
* ancestor directories, since that is the chain Codex loads while working
|
|
79593
|
+
* there. When the root file alone is over the budget every chain is, so only
|
|
79594
|
+
* the root is reported; likewise each over-budget nested chain is reported
|
|
79595
|
+
* once, at its shallowest directory. With several output roots the warning
|
|
79596
|
+
* names the root it refers to. An output root below the git root is measured
|
|
79597
|
+
* without the parent directories' files Codex would also load, so the
|
|
79598
|
+
* estimate can come out low there. The budget checked is the tool's default: a value
|
|
79599
|
+
* raised by hand in the tool's own config is not read. Global scope is not
|
|
79600
|
+
* checked because Codex loads the personal `~/.codex/AGENTS.md` outside the
|
|
79601
|
+
* project budget.
|
|
79602
|
+
*/
|
|
79603
|
+
warnForInstructionBudget({ toolRules, meta }) {
|
|
79604
|
+
const budget = meta.projectInstructionBudget;
|
|
79605
|
+
if (budget === void 0 || this.global) return;
|
|
79606
|
+
const files = toolRules.map((rule) => ({
|
|
79607
|
+
dir: normalizeRelativeDir(rule.getRelativeDirPath()),
|
|
79608
|
+
path: toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())),
|
|
79609
|
+
bytes: Buffer.byteLength(addTrailingNewline(rule.getFileContent()), "utf8")
|
|
79610
|
+
}));
|
|
79611
|
+
const isAncestorOrSelf = (ancestor, dir) => ancestor === "." || ancestor === dir || dir.startsWith(`${ancestor}/`);
|
|
79612
|
+
const location = this.outputRoot === process.cwd() ? "" : ` under '${this.outputRoot}'`;
|
|
79613
|
+
const rootFiles = files.filter(({ dir }) => dir === ".");
|
|
79614
|
+
const rootBytes = rootFiles.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
79615
|
+
if (rootBytes > budget.bytes) {
|
|
79616
|
+
this.logger.warn(`The generated ${this.toolTarget} root instruction file${location} (${rootFiles.map(({ path }) => path).join(", ")}) is ${rootBytes} bytes, over the ${budget.bytes}-byte limit. ${budget.remedy}`);
|
|
79617
|
+
return;
|
|
79618
|
+
}
|
|
79619
|
+
const reportedDirs = [];
|
|
79620
|
+
const nestedFiles = files.filter(({ dir }) => dir !== ".").toSorted((a, b) => a.dir.split("/").length - b.dir.split("/").length);
|
|
79621
|
+
for (const file of nestedFiles) {
|
|
79622
|
+
if (reportedDirs.some((dir) => isAncestorOrSelf(dir, file.dir))) continue;
|
|
79623
|
+
const chain = files.filter(({ dir }) => isAncestorOrSelf(dir, file.dir));
|
|
79624
|
+
const chainBytes = chain.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
79625
|
+
if (chainBytes > budget.bytes) {
|
|
79626
|
+
reportedDirs.push(file.dir);
|
|
79627
|
+
this.logger.warn(`The generated ${this.toolTarget} instruction files${location} loaded while working in '${file.dir}' (${chain.map(({ path }) => path).join(", ")}) total ${chainBytes} bytes, over the ${budget.bytes}-byte limit (as is every directory below it). ${budget.remedy}`);
|
|
79628
|
+
}
|
|
79629
|
+
}
|
|
79630
|
+
}
|
|
79631
|
+
/**
|
|
79488
79632
|
* Handle localRoot rules (only in non-global mode and when enabled). Mutates
|
|
79489
79633
|
* `toolRules` in place.
|
|
79490
79634
|
*/
|
|
@@ -80156,14 +80300,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
80156
80300
|
* decides for all of them: its `pi.contextFile` is copied onto the non-root
|
|
80157
80301
|
* rules, and the flag set only on a non-root rule is dropped with a warning.
|
|
80158
80302
|
*/
|
|
80159
|
-
alignPiContextFile(rules) {
|
|
80303
|
+
alignPiContextFile(rules, { warn = true } = {}) {
|
|
80160
80304
|
if (this.toolTarget !== "pi") return rules;
|
|
80161
80305
|
const factory = this.getFactory(this.toolTarget);
|
|
80162
80306
|
const targeted = rules.filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
80163
80307
|
const rootContextFile = targeted.some((rule) => rule.getFrontmatter().root === true && rule.getFrontmatter().pi?.contextFile === "override") ? "override" : void 0;
|
|
80164
80308
|
const mismatched = targeted.filter((rule) => rule.getFrontmatter().pi?.contextFile !== rootContextFile);
|
|
80165
80309
|
if (mismatched.length === 0) return rules;
|
|
80166
|
-
if (rootContextFile === void 0) this.logger.warn(`pi.contextFile is set on ${mismatched.length} non-root rule(s) but not on the root rule, so it is ignored: Pi folds every rule body into the root context file, and emitting AGENTS.override.md for some of them would hide the rest. Set it on the root rule instead: ${formatRulePaths(mismatched)}`);
|
|
80310
|
+
if (warn && rootContextFile === void 0) this.logger.warn(`pi.contextFile is set on ${mismatched.length} non-root rule(s) but not on the root rule, so it is ignored: Pi folds every rule body into the root context file, and emitting AGENTS.override.md for some of them would hide the rest. Set it on the root rule instead: ${formatRulePaths(mismatched)}`);
|
|
80167
80311
|
const mismatchedSet = new Set(mismatched);
|
|
80168
80312
|
return rules.map((rule) => {
|
|
80169
80313
|
if (!mismatchedSet.has(rule)) return rule;
|
|
@@ -80217,14 +80361,72 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
80217
80361
|
* duplication check the same way `loadRulesyncFiles`'s global-mode branch
|
|
80218
80362
|
* excludes it from `nonRootRules`: `generate` ignores `localRoot` entirely
|
|
80219
80363
|
* in global mode, so such a rule is never actually folded into the global
|
|
80220
|
-
* root output and warning about it here would be inaccurate.
|
|
80364
|
+
* root output and warning about it here would be inaccurate. The same goes
|
|
80365
|
+
* for a tool that writes `localRoot` to a dedicated local file.
|
|
80366
|
+
*
|
|
80367
|
+
* Non-root rules written to a file other than the root (nested
|
|
80368
|
+
* per-directory files, Pi's `APPEND_SYSTEM.md`) get a separate warning when
|
|
80369
|
+
* that file already exists and would import under a different name.
|
|
80221
80370
|
*/
|
|
80222
80371
|
async warnForFoldImportDuplicationRisk() {
|
|
80223
80372
|
const factory = this.getFactory(this.toolTarget);
|
|
80224
80373
|
if (factory.meta.collisionPolicy !== "fold") return;
|
|
80225
|
-
const
|
|
80226
|
-
|
|
80227
|
-
|
|
80374
|
+
const targetedRules = this.alignPiContextFile(await this.loadMergedRulesyncRules(), { warn: false }).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
80375
|
+
let includeLocalRoot = !this.global;
|
|
80376
|
+
try {
|
|
80377
|
+
includeLocalRoot &&= resolveIncludeLocalRoot(this.featureOptions);
|
|
80378
|
+
} catch {}
|
|
80379
|
+
const targetedNonRootRules = targetedRules.filter((rule) => !rule.getFrontmatter().root && !(rule.getFrontmatter().localRoot && (!includeLocalRoot || factory.meta.localRootMode === "separate-local-file")));
|
|
80380
|
+
const rootPaths = /* @__PURE__ */ new Set();
|
|
80381
|
+
const { root } = factory.class.getSettablePaths({ global: this.global });
|
|
80382
|
+
if (root) rootPaths.add(node_path.posix.join(normalizeRelativeDir(root.relativeDirPath), root.relativeFilePath));
|
|
80383
|
+
for (const rulesyncRule of targetedRules.filter((rule) => rule.getFrontmatter().root)) {
|
|
80384
|
+
const toolRule = this.toToolRuleForImportCheck({
|
|
80385
|
+
factory,
|
|
80386
|
+
rulesyncRule
|
|
80387
|
+
});
|
|
80388
|
+
rootPaths.add(toolOutputPath(toolRule));
|
|
80389
|
+
}
|
|
80390
|
+
const separateRules = [];
|
|
80391
|
+
const foldedRules = [];
|
|
80392
|
+
let localRootCounted = false;
|
|
80393
|
+
for (const rulesyncRule of targetedNonRootRules) {
|
|
80394
|
+
if (rulesyncRule.getFrontmatter().localRoot) {
|
|
80395
|
+
if (!localRootCounted) {
|
|
80396
|
+
foldedRules.push(rulesyncRule);
|
|
80397
|
+
localRootCounted = true;
|
|
80398
|
+
}
|
|
80399
|
+
continue;
|
|
80400
|
+
}
|
|
80401
|
+
const toolRule = this.toToolRuleForImportCheck({
|
|
80402
|
+
factory,
|
|
80403
|
+
rulesyncRule
|
|
80404
|
+
});
|
|
80405
|
+
if (rootPaths.size === 0 || rootPaths.has(toolOutputPath(toolRule))) foldedRules.push(rulesyncRule);
|
|
80406
|
+
else separateRules.push({
|
|
80407
|
+
toolRule,
|
|
80408
|
+
rulesyncRule
|
|
80409
|
+
});
|
|
80410
|
+
}
|
|
80411
|
+
if (foldedRules.length > 0) this.logger.warn(`Importing ${this.toolTarget}'s root file will re-add content already folded from ${formatRulePaths(foldedRules)}: ${this.toolTarget} concatenates every non-root rule into its single root output file, so the imported copy duplicates them the next time you run \`rulesync generate --targets ${this.toolTarget}\`. Review the imported rule and remove the duplicated content, or remove the original non-root rule files, before generating again.`);
|
|
80412
|
+
const duplicated = [];
|
|
80413
|
+
for (const conversion of separateRules) {
|
|
80414
|
+
const { toolRule, rulesyncRule } = conversion;
|
|
80415
|
+
if (toolRule.toRulesyncRule().getRelativeFilePath().toLowerCase() === rulesyncRule.getRelativeFilePath().toLowerCase()) continue;
|
|
80416
|
+
if (await fileExists(toolRule.getFilePath())) duplicated.push(conversion);
|
|
80417
|
+
}
|
|
80418
|
+
if (duplicated.length > 0) {
|
|
80419
|
+
const fileNames = [...new Set(duplicated.map(({ toolRule }) => toolRule.getRelativeFilePath()))].join(", ");
|
|
80420
|
+
this.logger.warn(`Importing ${this.toolTarget}'s ${fileNames} files outside the root file will re-add content already written from ${formatRulePaths(duplicated.map(({ rulesyncRule }) => rulesyncRule))}: each such file is imported as its own rule under a name derived from its path, so the imported copy and the original rule resolve to the same file and are concatenated the next time you run \`rulesync generate --targets ${this.toolTarget}\`. Remove one of the two copies before generating again.`);
|
|
80421
|
+
}
|
|
80422
|
+
}
|
|
80423
|
+
toToolRuleForImportCheck({ factory, rulesyncRule }) {
|
|
80424
|
+
return factory.class.fromRulesyncRule({
|
|
80425
|
+
outputRoot: this.outputRoot,
|
|
80426
|
+
rulesyncRule,
|
|
80427
|
+
validate: false,
|
|
80428
|
+
global: this.global
|
|
80429
|
+
});
|
|
80228
80430
|
}
|
|
80229
80431
|
/**
|
|
80230
80432
|
* Implementation of abstract method from FeatureProcessor
|
|
@@ -80518,6 +80720,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
80518
80720
|
function isPackagingToolTarget(toolTarget) {
|
|
80519
80721
|
return PACKAGING_TOOL_TARGETS.includes(toolTarget);
|
|
80520
80722
|
}
|
|
80723
|
+
/**
|
|
80724
|
+
* Packaging targets write into a plugin directory and support project scope
|
|
80725
|
+
* only. In global mode their output root would be the home directory, so
|
|
80726
|
+
* callers reject or skip them up front instead of checking all of $HOME.
|
|
80727
|
+
*/
|
|
80728
|
+
function formatPackagingTargetGlobalMessage(toolTarget) {
|
|
80729
|
+
return `Target '${toolTarget}' is a plugin packaging target and supports only project scope. Re-run without '--global'.`;
|
|
80730
|
+
}
|
|
80521
80731
|
async function assertPluginRootSafe(params) {
|
|
80522
80732
|
if (!isPackagingToolTarget(params.toolTarget)) return;
|
|
80523
80733
|
await assertDirectoryIfExists(params.outputRoot);
|
|
@@ -81437,14 +81647,17 @@ function warnUnsupportedTargets(params) {
|
|
|
81437
81647
|
} catch {
|
|
81438
81648
|
oppositeScopeTargets = [];
|
|
81439
81649
|
}
|
|
81440
|
-
for (const target of config.getTargets())
|
|
81441
|
-
|
|
81442
|
-
if (
|
|
81443
|
-
|
|
81444
|
-
|
|
81445
|
-
|
|
81446
|
-
|
|
81447
|
-
|
|
81650
|
+
for (const target of config.getTargets()) {
|
|
81651
|
+
if (config.getGlobal() && isPackagingToolTarget(target)) continue;
|
|
81652
|
+
if (!supportedTargets.includes(target) && config.getFeatures(target).includes(featureName)) {
|
|
81653
|
+
const simulateOption = SIMULATE_OPTION_MAP[featureName];
|
|
81654
|
+
if (simulateOption && simulatedTargets.includes(target)) logger.warn(`Target '${target}' only supports simulated '${featureName}'. Use '${simulateOption}' to enable it. Skipping.`);
|
|
81655
|
+
else if (oppositeScopeTargets.includes(target)) {
|
|
81656
|
+
const supportedScope = config.getGlobal() ? "project" : "global";
|
|
81657
|
+
const retry = config.getGlobal() ? "without '--global'" : "with '--global'";
|
|
81658
|
+
logger.warn(`Target '${target}' supports the feature '${featureName}' only in ${supportedScope} scope. Re-run ${retry}. Skipping.`);
|
|
81659
|
+
} else logger.warn(`Target '${target}' does not support the feature '${featureName}'. Skipping.`);
|
|
81660
|
+
}
|
|
81448
81661
|
}
|
|
81449
81662
|
}
|
|
81450
81663
|
/**
|
|
@@ -81697,10 +81910,16 @@ async function generate(params) {
|
|
|
81697
81910
|
const { config, logger } = params;
|
|
81698
81911
|
resetRootShadowingWarnings({ logger });
|
|
81699
81912
|
resetRunWarningState();
|
|
81700
|
-
for (const toolTarget of config.getTargets())
|
|
81701
|
-
toolTarget
|
|
81702
|
-
|
|
81703
|
-
|
|
81913
|
+
for (const toolTarget of config.getTargets()) {
|
|
81914
|
+
if (config.getGlobal() && isPackagingToolTarget(toolTarget)) {
|
|
81915
|
+
logger.warn(`${formatPackagingTargetGlobalMessage(toolTarget)} Skipping.`);
|
|
81916
|
+
continue;
|
|
81917
|
+
}
|
|
81918
|
+
for (const outputRoot of config.getOutputRoots(toolTarget)) await assertPluginRootSafe({
|
|
81919
|
+
toolTarget,
|
|
81920
|
+
outputRoot
|
|
81921
|
+
});
|
|
81922
|
+
}
|
|
81704
81923
|
await warnSkillSubagentNameCollisions({
|
|
81705
81924
|
config,
|
|
81706
81925
|
logger
|
|
@@ -82317,6 +82536,7 @@ function getToolOutputRoot({ config, tool }) {
|
|
|
82317
82536
|
async function importFromTool(params) {
|
|
82318
82537
|
const { config, tool, logger } = params;
|
|
82319
82538
|
resetRunWarningState();
|
|
82539
|
+
if (config.getGlobal() && isPackagingToolTarget(tool)) throw new Error(formatPackagingTargetGlobalMessage(tool));
|
|
82320
82540
|
await assertPluginRootSafe({
|
|
82321
82541
|
toolTarget: tool,
|
|
82322
82542
|
outputRoot: getToolOutputRoot({
|
|
@@ -82377,7 +82597,7 @@ async function importRulesCore(params) {
|
|
|
82377
82597
|
if (!config.getFeatures(tool).includes("rules")) return 0;
|
|
82378
82598
|
const global = config.getGlobal();
|
|
82379
82599
|
if (!RulesProcessor.getToolTargets({ global }).includes(tool)) return 0;
|
|
82380
|
-
const
|
|
82600
|
+
const baseParams = {
|
|
82381
82601
|
outputRoot: getToolOutputRoot({
|
|
82382
82602
|
config,
|
|
82383
82603
|
tool
|
|
@@ -82385,13 +82605,23 @@ async function importRulesCore(params) {
|
|
|
82385
82605
|
toolTarget: tool,
|
|
82386
82606
|
global,
|
|
82387
82607
|
logger
|
|
82388
|
-
}
|
|
82608
|
+
};
|
|
82609
|
+
const rulesProcessor = new RulesProcessor(baseParams);
|
|
82389
82610
|
const toolFiles = await rulesProcessor.loadToolFiles();
|
|
82390
82611
|
if (toolFiles.length === 0) {
|
|
82391
82612
|
logger.warn(`No rule files found for ${tool}. Skipping import.`);
|
|
82392
82613
|
return 0;
|
|
82393
82614
|
}
|
|
82394
|
-
|
|
82615
|
+
try {
|
|
82616
|
+
await new RulesProcessor({
|
|
82617
|
+
...baseParams,
|
|
82618
|
+
inputRoots: config.getInputRoots(),
|
|
82619
|
+
deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
|
|
82620
|
+
featureOptions: config.getFeatureOptions(tool, "rules")
|
|
82621
|
+
}).warnForFoldImportDuplicationRisk();
|
|
82622
|
+
} catch (error) {
|
|
82623
|
+
logger.warn(`Skipped the ${tool} import duplication check: ${formatError(error)}`);
|
|
82624
|
+
}
|
|
82395
82625
|
const rulesyncFiles = await rulesProcessor.convertToolFilesToRulesyncFiles(toolFiles);
|
|
82396
82626
|
const { count: writtenCount } = await rulesProcessor.writeAiFiles(rulesyncFiles);
|
|
82397
82627
|
if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} rule files`);
|