You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This week’s bouquet is hot plastic, damp Markdown, and the faint metallic tang of catch {}. The repo clearly does useful work, but it also keeps solving problems by adding a second code path, a magic deletion list, or a 1,423-line helper that desperately wants to be a data table. 💩
I found enough real problems without wasting time on style trivia. Thanks for that, I guess.
Nested discovery crawls every non-hidden directory, which is how you end up treating node_modules like a first-class configuration source. 💩
findAllRulerDirs() only skips hidden directories and nested git repos. In a real project that still means node_modules, dist, build, coverage, vendor, fixtures, and other generated junk get walked recursively.
That is wasted IO at best and false-positive .ruler discovery at worst. This repo already ships big tests/ and tmp-fixtures/ trees, so the slowdown is not hypothetical; it is merely waiting for a larger codebase to become embarrassing.
if(entry.name==='.ruler'){rulerDirs.push(fullPath);}else{// Recursively search subdirectories (but skip hidden directories like .git)if(!entry.name.startsWith('.')){// Do not cross git repository boundaries (except the starting root)constgitDir=path.join(fullPath,'.git');try{constgitStat=awaitfs.stat(gitDir);if(gitStat.isDirectory()&&path.resolve(fullPath)!==rootPath){continue;}}catch{// no .git boundary, continue traversal}awaitfindRulerDirs(fullPath);}}
Suggestions
Skip common generated/vendor dirs by default: node_modules, dist, build, coverage, target, vendor, out, etc.
Add config/CLI excludes for nested discovery instead of hard-wiring “everything that isn’t hidden.”
Add tests that prove discovery ignores fixtures and build artifacts.
Revert Cleanup Deletes User Files On Vibes
Revert has a filename kill list—including config.toml—because apparently provenance was too mainstream. 🤮
removeAdditionalAgentFiles() deletes based on hard-coded paths instead of “Ruler created this” evidence. config.toml is a completely ordinary root file name, so this is filesystem roulette with a loaded chamber.
A .bak check is not provenance. “No backup” means “we don’t know,” not “safe to unlink,” unless the tool has fully committed to being a tiny, overconfident goblin.
case'Open Hands':
// For Open Hands, we target the main config file, not a separate mcp.jsoncandidates.push(path.join(projectRoot,'config.toml'));break;
Suggestions
Track generated files in a manifest or write a provenance marker into managed configs.
Only delete ambiguous paths when a marker or backup proves Ruler owns them.
Make extra cleanup for generic filenames opt-in with a loud --force-style flag.
Config Loading Has Split Personality Disorder
Two different loaders define “the current rules,” and they do not even agree on which Markdown files exist.
apply-engine uses loadConfig() plus recursive readMarkdownFiles() for actual application, then separately calls loadUnifiedConfig() for MCP. So the repo already has two sources of truth before the first write begins. Lovely.
Worse, the unified loader only readdir()s top-level *.md files, while the real loader walks recursively and applies precedence logic. Hashes, diagnostics, and future behavior drift are basically built into the architecture.
if(isDir){// Skip .ruler/skills; skills are propagated separately and should not be concatenatedconstrelativeFromRoot=path.relative(rulerDir,fullPath);constisSkillsDir=relativeFromRoot===SKILLS_DIR||relativeFromRoot.startsWith(`${SKILLS_DIR}${path.sep}`);if(isSkillsDir){continue;}awaitwalk(fullPath);}elseif(isFile&&entry.name.endsWith('.md')){constcontent=awaitfs.readFile(fullPath,'utf8');mdFiles.push({path: fullPath, content });}
src/core/UnifiedConfigLoader.ts lines 109–112: link
Build one canonical resolver for config, rules, and MCP.
Reuse the same recursive discovery and precedence logic everywhere.
Make downstream consumers derive from the resolved model instead of reparsing the filesystem differently.
MCP Handling Has Two Bosses
MCP handling is split between adapters and the engine, then defended with a stringly skip list and a parse-error-to-empty-object trap. 🔥
applyConfigurationsToAgents() hands MCP into each agent and then immediately runs a second handleMcpConfiguration() pass. Several adapters also merge/write MCP themselves, so ownership is duplicated and the abstractions are already fighting in the parking lot.
The skip list is brittle enough that it already misses Amazon Q (amazon-q-cli vs amazonqcli), and the generic reader returns {} on any JSON failure. So malformed user config can quietly become “empty config, now overwritten.” Amazing. No notes. Actually, many notes.
// Handle MCP configuration if enabled and providedconstmcpEnabled=agentConfig?.mcp?.enabled??true;if(mcpEnabled&&rulerMcpJson){constmcpPath=path.resolve(projectRoot,agentConfig?.outputPathConfig??outputPaths['mcp'],);constmcpStrategy=agentConfig?.mcp?.strategy??'merge';
Choose one MCP owner: engine-only with agent-specific transformers, or adapter-only with zero engine fallback.
Replace string comparisons with explicit agent capability hooks.
Treat parse errors differently from missing files; fail loudly or require an explicit repair flag.
Add regression tests for Amazon Q skip behavior and malformed native config preservation.
SkillsProcessor Is a Copy-Paste Cathedral
SkillsProcessor.ts is a 1,423-line copy-paste cathedral where every new agent gets another hand-written filesystem ritual.
The file manually dispatches target by target and repeats the same mkdir → temp dir → copy → rm → rename → cleanup flow over and over. This is not extensibility; it is clerical labor wearing TypeScript.
Every repetition is another chance for drift in temp-dir naming, logs, edge-case handling, and bug fixes. When a new skills-capable agent means copy-pasting yet another almost-identical function, the design has already submitted its resignation.
if(selectedTargets.has('claude')){logVerboseInfo(`Copying skills to ${CLAUDE_SKILLS_PATH} for Claude Code, GitHub Copilot, and KiloCode`,verbose,dryRun,);awaitpropagateSkillsForClaude(projectRoot,{ dryRun });}if(selectedTargets.has('codex')){logVerboseInfo(`Copying skills to ${CODEX_SKILLS_PATH} for OpenAI Codex CLI`,verbose,dryRun,);awaitpropagateSkillsForCodex(projectRoot,{ dryRun });}
awaitfs.mkdir(claudeDir,{recursive: true});// Use atomic replace: copy to temp, then renameconsttempDir=path.join(claudeDir,`skills.tmp-${Date.now()}`);try{// Copy to temp directoryawaitcopySkillsDirectory(skillsDir,tempDir);// Atomically replace the target// First, remove existing target if it existstry{awaitfs.rm(claudeSkillsPath,{recursive: true,force: true});}catch{// Target didn't exist, that's fine}// Rename temp to targetawaitfs.rename(tempDir,claudeSkillsPath);
// Ensure .codex directory existsawaitfs.mkdir(codexDir,{recursive: true});// Use atomic replace: copy to temp, then renameconsttempDir=path.join(codexDir,`skills.tmp-${Date.now()}`);try{// Copy to temp directoryawaitcopySkillsDirectory(skillsDir,tempDir);// Atomically replace the target// First, remove existing target if it existstry{awaitfs.rm(codexSkillsPath,{recursive: true,force: true});}catch{// Target didn't exist, that's fine}// Rename temp to targetawaitfs.rename(tempDir,codexSkillsPath);
Suggestions
Move skills target metadata into a single table: identifiers, labels, target paths, and capability flags.
Implement generic syncSkillsTarget() and cleanupSkillsTarget() helpers.
Restrict catches to expected conditions like ENOENT; surface the rest instead of normalizing every error into a shrug.
Revert Ignores Invalid Agent Names
revert treats invalid agent names as a gentle suggestion, which is a ridiculous thing for a destructive command to do.
resolveSelectedAgents() correctly throws on unknown agents, but revert() catches that case and keeps going “for backward compatibility.” That makes typos easy to miss during the command that is supposed to clean things up safely.
The integration tests explicitly bless this behavior, so the inconsistency with apply is institutionalized. If the user asked to revert a nonexistent agent, the correct response is failure, not improv comedy with file deletion.
try{selected=resolveSelectedAgents(config,agents);}catch(error){// For backward compatibility, revert continues with available agents if some are invalid// This preserves the original behavior where invalid agents were silently ignoredif(errorinstanceofError&&error.message.includes('Invalid agent specified')){logVerbose(
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
This Codebase Smells!
This week’s bouquet is hot plastic, damp Markdown, and the faint metallic tang of
catch {}. The repo clearly does useful work, but it also keeps solving problems by adding a second code path, a magic deletion list, or a 1,423-line helper that desperately wants to be a data table. 💩I found enough real problems without wasting time on style trivia. Thanks for that, I guess.
Table of Contents
Recursive Scans Raid Generated Directories
Nested discovery crawls every non-hidden directory, which is how you end up treating
node_moduleslike a first-class configuration source. 💩findAllRulerDirs()only skips hidden directories and nested git repos. In a real project that still meansnode_modules,dist,build,coverage,vendor, fixtures, and other generated junk get walked recursively..rulerdiscovery at worst. This repo already ships bigtests/andtmp-fixtures/trees, so the slowdown is not hypothetical; it is merely waiting for a larger codebase to become embarrassing.Files
src/core/FileSystemUtils.tsCode
src/core/FileSystemUtils.tslines 237–255: linkSuggestions
node_modules,dist,build,coverage,target,vendor,out, etc.Revert Cleanup Deletes User Files On Vibes
Revert has a filename kill list—including
config.toml—because apparently provenance was too mainstream. 🤮removeAdditionalAgentFiles()deletes based on hard-coded paths instead of “Ruler created this” evidence.config.tomlis a completely ordinary root file name, so this is filesystem roulette with a loaded chamber..bakcheck is not provenance. “No backup” means “we don’t know,” not “safe to unlink,” unless the tool has fully committed to being a tiny, overconfident goblin.Files
src/core/revert-engine.tssrc/paths/mcp.tsCode
src/core/revert-engine.tslines 311–319: linksrc/core/revert-engine.tslines 333–349: linksrc/paths/mcp.tslines 33–36: linkSuggestions
--force-style flag.Config Loading Has Split Personality Disorder
Two different loaders define “the current rules,” and they do not even agree on which Markdown files exist.
apply-engineusesloadConfig()plus recursivereadMarkdownFiles()for actual application, then separately callsloadUnifiedConfig()for MCP. So the repo already has two sources of truth before the first write begins. Lovely.readdir()s top-level*.mdfiles, while the real loader walks recursively and applies precedence logic. Hashes, diagnostics, and future behavior drift are basically built into the architecture.Files
src/core/apply-engine.tssrc/core/FileSystemUtils.tssrc/core/UnifiedConfigLoader.tsCode
src/core/apply-engine.tslines 283–296: linksrc/core/FileSystemUtils.tslines 86–98: linksrc/core/UnifiedConfigLoader.tslines 109–112: linkSuggestions
MCP Handling Has Two Bosses
MCP handling is split between adapters and the engine, then defended with a stringly skip list and a parse-error-to-empty-object trap. 🔥
applyConfigurationsToAgents()hands MCP into each agent and then immediately runs a secondhandleMcpConfiguration()pass. Several adapters also merge/write MCP themselves, so ownership is duplicated and the abstractions are already fighting in the parking lot.amazon-q-clivsamazonqcli), and the generic reader returns{}on any JSON failure. So malformed user config can quietly become “empty config, now overwritten.” Amazing. No notes. Actually, many notes.Files
src/core/apply-engine.tssrc/agents/AmazonQCliAgent.tssrc/paths/mcp.tsCode
src/core/apply-engine.tslines 481–487: linksrc/core/apply-engine.tslines 492–499: linksrc/agents/AmazonQCliAgent.tslines 45–53: linksrc/core/apply-engine.tslines 673–676: linksrc/agents/AmazonQCliAgent.tslines 15–17: linksrc/paths/mcp.tslines 84–89: linkSuggestions
SkillsProcessor Is a Copy-Paste Cathedral
SkillsProcessor.tsis a 1,423-line copy-paste cathedral where every new agent gets another hand-written filesystem ritual.mkdir → temp dir → copy → rm → rename → cleanupflow over and over. This is not extensibility; it is clerical labor wearing TypeScript.Files
src/core/SkillsProcessor.tsCode
src/core/SkillsProcessor.tslines 557–573: linksrc/core/SkillsProcessor.tslines 703–722: linksrc/core/SkillsProcessor.tslines 761–780: linkSuggestions
syncSkillsTarget()andcleanupSkillsTarget()helpers.ENOENT; surface the rest instead of normalizing every error into a shrug.Revert Ignores Invalid Agent Names
reverttreats invalid agent names as a gentle suggestion, which is a ridiculous thing for a destructive command to do.resolveSelectedAgents()correctly throws on unknown agents, butrevert()catches that case and keeps going “for backward compatibility.” That makes typos easy to miss during the command that is supposed to clean things up safely.applyis institutionalized. If the user asked to revert a nonexistent agent, the correct response is failure, not improv comedy with file deletion.Files
src/revert.tssrc/core/agent-selection.tstests/integration/revert-cli.test.tsCode
src/revert.tslines 56–67: linksrc/core/agent-selection.tslines 37–41: linktests/integration/revert-cli.test.tslines 139–146: linkSuggestions
revertuse the same strict validation path asapply.--ignore-invalid-agents.All reactions