gitea push

This commit is contained in:
2026-05-09 12:19:29 -06:00
parent 06113c95b8
commit 429461e985
1481 changed files with 74306 additions and 52475 deletions
@@ -58,5 +58,8 @@ class ClaudeAgent extends AbstractAgent_1.AbstractAgent {
supportsNativeSkills() {
return true;
}
supportsNativeSubagents() {
return true;
}
}
exports.ClaudeAgent = ClaudeAgent;
@@ -149,5 +149,8 @@ class CodexCliAgent {
supportsNativeSkills() {
return true;
}
supportsNativeSubagents() {
return true;
}
}
exports.CodexCliAgent = CodexCliAgent;
@@ -42,5 +42,8 @@ class CopilotAgent {
supportsNativeSkills() {
return true;
}
supportsNativeSubagents() {
return true;
}
}
exports.CopilotAgent = CopilotAgent;
@@ -33,5 +33,8 @@ class CursorAgent extends AgentsMdAgent_1.AgentsMdAgent {
supportsNativeSkills() {
return true;
}
supportsNativeSubagents() {
return true;
}
}
exports.CursorAgent = CursorAgent;
+4
View File
@@ -77,6 +77,10 @@ function run() {
.option('skills', {
type: 'boolean',
description: 'Enable/disable skills support (experimental, default: enabled)',
})
.option('subagents', {
type: 'boolean',
description: 'Enable/disable subagents support (experimental, default: enabled)',
});
}, handlers_1.applyHandler)
.command('init', 'Scaffold a .ruler directory with default files', (y) => {
+9 -1
View File
@@ -114,8 +114,16 @@ async function applyHandler(argv) {
else {
skillsEnabled = undefined; // Let config/default decide
}
// Determine subagents preference: CLI > TOML > Default (enabled)
let subagentsEnabled;
if (argv.subagents !== undefined) {
subagentsEnabled = argv.subagents;
}
else {
subagentsEnabled = undefined; // Let config/default decide
}
try {
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled, gitignoreLocalPreference);
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled, gitignoreLocalPreference, subagentsEnabled);
console.log('Ruler apply completed successfully.');
}
catch (err) {
+7 -1
View File
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.FACTORY_SKILLS_PATH = exports.WINDSURF_SKILLS_PATH = exports.CURSOR_SKILLS_PATH = exports.JUNIE_SKILLS_PATH = exports.GEMINI_SKILLS_PATH = exports.ROO_SKILLS_PATH = exports.VIBE_SKILLS_PATH = exports.GOOSE_SKILLS_PATH = exports.PI_SKILLS_PATH = exports.OPENCODE_SKILLS_PATH = exports.CODEX_SKILLS_PATH = exports.CLAUDE_SKILLS_PATH = exports.RULER_SKILLS_PATH = exports.SKILLS_DIR = exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
exports.COPILOT_SUBAGENTS_PATH = exports.CODEX_SUBAGENTS_PATH = exports.CURSOR_SUBAGENTS_PATH = exports.CLAUDE_SUBAGENTS_PATH = exports.RULER_SUBAGENTS_PATH = exports.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.FACTORY_SKILLS_PATH = exports.WINDSURF_SKILLS_PATH = exports.CURSOR_SKILLS_PATH = exports.JUNIE_SKILLS_PATH = exports.GEMINI_SKILLS_PATH = exports.ROO_SKILLS_PATH = exports.VIBE_SKILLS_PATH = exports.GOOSE_SKILLS_PATH = exports.PI_SKILLS_PATH = exports.OPENCODE_SKILLS_PATH = exports.CODEX_SKILLS_PATH = exports.CLAUDE_SKILLS_PATH = exports.RULER_SKILLS_PATH = exports.SKILLS_DIR = exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
exports.actionPrefix = actionPrefix;
exports.createRulerError = createRulerError;
exports.logVerbose = logVerbose;
@@ -66,3 +66,9 @@ exports.WINDSURF_SKILLS_PATH = '.windsurf/skills';
exports.FACTORY_SKILLS_PATH = '.factory/skills';
exports.ANTIGRAVITY_SKILLS_PATH = '.agent/skills';
exports.SKILL_MD_FILENAME = 'SKILL.md';
// Subagents-related constants
exports.RULER_SUBAGENTS_PATH = '.ruler/agents';
exports.CLAUDE_SUBAGENTS_PATH = '.claude/agents';
exports.CURSOR_SUBAGENTS_PATH = '.cursor/agents';
exports.CODEX_SUBAGENTS_PATH = '.codex/agents';
exports.COPILOT_SUBAGENTS_PATH = '.github/agents';
+79 -1
View File
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports._resetLegacySubagentsWarningForTests = _resetLegacySubagentsWarningForTests;
exports.loadConfig = loadConfig;
const fs_1 = require("fs");
const path = __importStar(require("path"));
@@ -40,6 +41,21 @@ const os = __importStar(require("os"));
const toml_1 = require("@iarna/toml");
const zod_1 = require("zod");
const constants_1 = require("../constants");
// One-shot guard so the deprecation message fires once per process even when
// `loadConfig` is called multiple times (e.g. nested mode walks every
// `.ruler` directory).
let _legacySubagentsWarned = false;
function warnLegacySubagentsSection() {
if (_legacySubagentsWarned)
return;
_legacySubagentsWarned = true;
(0, constants_1.logWarn)('`[subagents]` is deprecated; rename it to `[agents]` in your ruler.toml. ' +
'The legacy section is honored for now and will be removed in a future release.');
}
/** Test helper — re-arms the deprecation guard so suites can assert it fires. */
function _resetLegacySubagentsWarningForTests() {
_legacySubagentsWarned = false;
}
const mcpConfigSchema = zod_1.z
.object({
enabled: zod_1.z.boolean().optional(),
@@ -55,9 +71,21 @@ const agentConfigSchema = zod_1.z
mcp: mcpConfigSchema,
})
.optional();
// `[agents]` is a heterogeneous table that holds two unrelated kinds of keys:
// - reserved subagent-control booleans (`enabled`, `include_in_rules`)
// - one nested table per coding-agent integration (`[agents.claude]`, etc.)
// Reserved keys are validated by the object shape; everything else falls
// through `catchall` and is treated as a per-agent config record.
const SUBAGENT_RESERVED_KEYS = new Set(['enabled', 'include_in_rules']);
const rulerConfigSchema = zod_1.z.object({
default_agents: zod_1.z.array(zod_1.z.string()).optional(),
agents: zod_1.z.record(zod_1.z.string(), agentConfigSchema).optional(),
agents: zod_1.z
.object({
enabled: zod_1.z.boolean().optional(),
include_in_rules: zod_1.z.boolean().optional(),
})
.catchall(agentConfigSchema)
.optional(),
mcp: zod_1.z
.object({
enabled: zod_1.z.boolean().optional(),
@@ -75,6 +103,16 @@ const rulerConfigSchema = zod_1.z.object({
enabled: zod_1.z.boolean().optional(),
})
.optional(),
// Deprecated: kept in the schema only so that legacy `[subagents]` blocks
// are preserved through validation. The parser reads from here as a
// fallback when the new `[agents]` keys are absent and emits a one-time
// deprecation warning. Remove in the next minor release.
subagents: zod_1.z
.object({
enabled: zod_1.z.boolean().optional(),
include_in_rules: zod_1.z.boolean().optional(),
})
.optional(),
nested: zod_1.z.boolean().optional(),
});
/**
@@ -150,6 +188,11 @@ async function loadConfig(options) {
: {};
const agentConfigs = {};
for (const [name, section] of Object.entries(agentsSection)) {
// Reserved subagent-control keys live alongside per-agent records in
// the same `[agents]` table; skip them here so we only process actual
// coding-agent integrations as agent configs.
if (SUBAGENT_RESERVED_KEYS.has(name))
continue;
if (section && typeof section === 'object') {
const sectionObj = section;
const cfg = {};
@@ -214,6 +257,40 @@ async function loadConfig(options) {
if (typeof rawSkillsSection.enabled === 'boolean') {
skillsConfig.enabled = rawSkillsSection.enabled;
}
// Subagent control lives under `[agents]` (alongside per-agent records).
// The reserved keys `enabled` and `include_in_rules` are pulled out here
// and surfaced internally as `LoadedConfig.subagents` for the rest of the
// codebase, which still uses the `Subagent*` naming.
//
// Backward-compatibility: the previous release used `[subagents]` for the
// same two keys. We still read those as a fallback when the matching
// `[agents]` key is absent, and emit a one-time deprecation warning so
// existing configs keep working while users migrate.
const rawLegacySubagentsSection = raw.subagents &&
typeof raw.subagents === 'object' &&
!Array.isArray(raw.subagents)
? raw.subagents
: {};
const legacyHasContent = typeof rawLegacySubagentsSection.enabled === 'boolean' ||
typeof rawLegacySubagentsSection.include_in_rules === 'boolean';
if (legacyHasContent) {
warnLegacySubagentsSection();
}
const subagentsConfig = {};
if (typeof agentsSection.enabled === 'boolean') {
subagentsConfig.enabled = agentsSection.enabled;
}
else if (typeof rawLegacySubagentsSection.enabled === 'boolean') {
subagentsConfig.enabled = rawLegacySubagentsSection.enabled;
}
if (typeof agentsSection.include_in_rules === 'boolean') {
subagentsConfig.include_in_rules =
agentsSection.include_in_rules;
}
else if (typeof rawLegacySubagentsSection.include_in_rules === 'boolean') {
subagentsConfig.include_in_rules =
rawLegacySubagentsSection.include_in_rules;
}
const nestedDefined = typeof raw.nested === 'boolean';
const nested = nestedDefined ? raw.nested : false;
return {
@@ -223,6 +300,7 @@ async function loadConfig(options) {
mcp: globalMcpConfig,
gitignore: gitignoreConfig,
skills: skillsConfig,
subagents: subagentsConfig,
nested,
nestedDefined,
};
+26 -4
View File
@@ -44,6 +44,7 @@ const fs_1 = require("fs");
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const constants_1 = require("../constants");
const SUBAGENTS_DIR_NAME = path.basename(constants_1.RULER_SUBAGENTS_PATH);
/**
* Gets the XDG config directory path, falling back to ~/.config if XDG_CONFIG_HOME is not set.
*/
@@ -93,9 +94,18 @@ async function findRulerDir(startPath, checkGlobal = true) {
/**
* Recursively reads all Markdown (.md) files in rulerDir, returning their paths and contents.
* Files are sorted alphabetically by path.
*
* `.ruler/skills/` is always skipped (skills are propagated separately).
* `.ruler/agents/` is skipped unless `options.includeAgents` is `true`.
*/
async function readMarkdownFiles(rulerDir) {
async function readMarkdownFiles(rulerDir, options = {}) {
const mdFiles = [];
const includeAgents = options.includeAgents === true;
// Tracks whether we skipped a `.ruler/agents` subtree so the root-AGENTS.md
// fallback below still recognises ruler content as present and does not
// resurrect a previously generated root AGENTS.md (which may itself contain
// the very agent docs we're now excluding).
let sawExcludedAgents = false;
// Gather all markdown files (recursive) first
async function walk(dir) {
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
@@ -115,13 +125,22 @@ async function readMarkdownFiles(rulerDir) {
}
}
if (isDir) {
// Skip .ruler/skills; skills are propagated separately and should not be concatenated
const relativeFromRoot = path.relative(rulerDir, fullPath);
// Skip .ruler/skills; skills are propagated separately and should not be concatenated
const isSkillsDir = relativeFromRoot === constants_1.SKILLS_DIR ||
relativeFromRoot.startsWith(`${constants_1.SKILLS_DIR}${path.sep}`);
if (isSkillsDir) {
continue;
}
// Skip .ruler/agents unless explicitly opted in via subagents.include_in_rules.
// Subagents are propagated separately to native locations and should not pollute
// the top-level rule concatenation by default.
const isAgentsDir = relativeFromRoot === SUBAGENTS_DIR_NAME ||
relativeFromRoot.startsWith(`${SUBAGENTS_DIR_NAME}${path.sep}`);
if (isAgentsDir && !includeAgents) {
sawExcludedAgents = true;
continue;
}
await walk(fullPath);
}
else if (isFile && entry.name.endsWith('.md')) {
@@ -170,9 +189,12 @@ async function readMarkdownFiles(rulerDir) {
const stat = await fs_1.promises.stat(rootAgentsPath);
if (stat.isFile()) {
const content = await fs_1.promises.readFile(rootAgentsPath, 'utf8');
// Check if this is a generated file and we have other .ruler files
// Check if this is a generated file and we have other .ruler files.
// `sawExcludedAgents` counts as "ruler content present" so a stale
// generated root AGENTS.md isn't resurrected when `.ruler/agents` was
// the only source under `.ruler` and is now being skipped.
const isGenerated = content.startsWith('<!-- Generated by Ruler -->');
const hasRulerFiles = others.length > 0 || primaryFile !== null;
const hasRulerFiles = others.length > 0 || primaryFile !== null || sawExcludedAgents;
// Additional check: if AGENTS.md contains ruler source comments and we have ruler files,
// it's likely a corrupted generated file that should be skipped
const containsRulerSources = content.includes('<!-- Source: .ruler/') ||
+17 -14
View File
@@ -56,25 +56,23 @@ const constants_1 = require("../constants");
async function loadNestedConfigurations(projectRoot, configPath, localOnly, resolvedNested) {
const { dirs: rulerDirs } = await findRulerDirectories(projectRoot, localOnly, true);
const results = [];
const rulerDirConfigs = await processIndependentRulerDirs(rulerDirs);
for (const { rulerDir, files } of rulerDirConfigs) {
// Load config first so we know whether `.ruler/agents/` should be included
// in the rule concatenation for each directory.
for (const rulerDir of rulerDirs) {
const config = await loadConfigForRulerDir(rulerDir, configPath, resolvedNested);
const files = await FileSystemUtils.readMarkdownFiles(rulerDir, {
includeAgents: shouldIncludeAgentsInRules(config),
});
results.push(await createHierarchicalConfiguration(rulerDir, files, config, configPath));
}
return results;
}
/**
* Processes each .ruler directory independently, returning configuration for each.
* Each .ruler directory gets its own rules (not merged with others).
* Returns true when `.ruler/agents/*.md` should be concatenated into the
* generated top-level rule files. Defaults to false.
*/
async function processIndependentRulerDirs(rulerDirs) {
const results = [];
// Process each .ruler directory independently
for (const rulerDir of rulerDirs) {
const files = await FileSystemUtils.readMarkdownFiles(rulerDir);
results.push({ rulerDir, files });
}
return results;
function shouldIncludeAgentsInRules(config) {
return config.subagents?.include_in_rules === true;
}
async function createHierarchicalConfiguration(rulerDir, files, config, cliConfigPath) {
await warnAboutLegacyMcpJson(rulerDir);
@@ -146,6 +144,8 @@ function cloneLoadedConfig(config) {
cliAgents: config.cliAgents ? [...config.cliAgents] : undefined,
mcp: config.mcp ? { ...config.mcp } : undefined,
gitignore: config.gitignore ? { ...config.gitignore } : undefined,
skills: config.skills ? { ...config.skills } : undefined,
subagents: config.subagents ? { ...config.subagents } : undefined,
nested: config.nested,
nestedDefined: config.nestedDefined,
};
@@ -203,8 +203,11 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
projectRoot,
configPath,
});
// Read rule files
const files = await FileSystemUtils.readMarkdownFiles(rulerDirs[0]);
// Read rule files. `.ruler/agents/` is only included when
// `[agents] include_in_rules = true`.
const files = await FileSystemUtils.readMarkdownFiles(rulerDirs[0], {
includeAgents: shouldIncludeAgentsInRules(config),
});
// Concatenate rules
const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(primaryDir));
// Load unified config to get merged MCP configuration
+42 -2
View File
@@ -53,6 +53,23 @@ function resolveSkillsEnabled(cliFlag, configSetting) {
? configSetting
: true; // default to enabled
}
/**
* Resolves subagents enabled state based on precedence:
* CLI flag > ruler.toml > default (disabled).
*
* When neither `[agents] enabled` (nor the legacy `[subagents] enabled`)
* nor a CLI flag is provided, propagation is disabled by default per spec.
* Subagent definitions are an opt-in feature — propagating them silently
* could leak runtime prompts into native subagent locations on projects
* that never intended to use the feature.
*/
function resolveSubagentsEnabled(cliFlag, configSetting) {
return cliFlag !== undefined
? cliFlag
: configSetting !== undefined
? configSetting
: false; // default to disabled — see spec: subagents must opt in
}
/**
* Applies ruler configurations for all supported AI agents.
* @param projectRoot Root directory of the project
@@ -62,7 +79,7 @@ function resolveSkillsEnabled(cliFlag, configSetting) {
* @param projectRoot Root directory of the project
* @param includedAgents Optional list of agent name filters (case-insensitive substrings)
*/
async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true, skillsEnabled, cliGitignoreLocal) {
async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true, skillsEnabled, cliGitignoreLocal, subagentsEnabled) {
// Load configuration and rules
(0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
if (configPath) {
@@ -100,6 +117,16 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
await propagateSkills(nestedRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
}
}
// Propagate subagents (mirrors skills handling for nested mode).
const subagentsEnabledResolved = resolveSubagentsEnabled(subagentsEnabled, rootConfig.subagents?.enabled);
{
const { propagateSubagents } = await Promise.resolve().then(() => __importStar(require('./core/SubagentsProcessor')));
for (const configEntry of hierarchicalConfigs) {
const nestedRoot = path.dirname(configEntry.rulerDir);
(0, constants_1.logVerbose)(`Propagating subagents for nested directory: ${nestedRoot}`, verbose);
await propagateSubagents(nestedRoot, selectedAgents, subagentsEnabledResolved, verbose, dryRun);
}
}
generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
}
else {
@@ -117,6 +144,12 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
const { propagateSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
await propagateSkills(projectRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
}
// Propagate subagents (mirrors skills handling).
const subagentsEnabledResolvedSingle = resolveSubagentsEnabled(subagentsEnabled, singleConfig.config.subagents?.enabled);
{
const { propagateSubagents } = await Promise.resolve().then(() => __importStar(require('./core/SubagentsProcessor')));
await propagateSubagents(projectRoot, selectedAgents, subagentsEnabledResolvedSingle, verbose, dryRun);
}
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
}
// Add skills-generated paths to gitignore if skills are enabled
@@ -126,7 +159,14 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
// Skills enabled by default or explicitly
const { getSkillsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
const skillsPaths = await getSkillsGitignorePaths(projectRoot, selectedAgents);
allGeneratedPaths = [...generatedPaths, ...skillsPaths];
allGeneratedPaths = [...allGeneratedPaths, ...skillsPaths];
}
// Add subagents-generated paths to gitignore if subagents are enabled.
const subagentsEnabledForGitignore = resolveSubagentsEnabled(subagentsEnabled, loadedConfig.subagents?.enabled);
if (subagentsEnabledForGitignore) {
const { getSubagentsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SubagentsProcessor')));
const subagentPaths = await getSubagentsGitignorePaths(projectRoot, selectedAgents);
allGeneratedPaths = [...allGeneratedPaths, ...subagentPaths];
}
await (0, apply_engine_1.updateGitignore)(projectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun, cliGitignoreLocal);
}